From 2fb0d216502e5c88bb0ce57bd1aa38ac570aff77 Mon Sep 17 00:00:00 2001 From: ryunsong-contentful <124832189+ryunsong-contentful@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:11:26 -0700 Subject: [PATCH 01/14] fix: google docs package.json scripts so that it deploys correctly in ci (#10350) --- .circleci/config.yml | 24 +++++++++++++----------- apps/google-analytics/package.json | 2 +- apps/google-docs/package.json | 10 +++++----- apps/jira/jira-app/package.json | 2 +- apps/slack/frontend/package.json | 2 +- 5 files changed, 21 insertions(+), 19 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7bea7b08af..487bcde3ad 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -112,12 +112,13 @@ jobs: - run: name: Deploy apps to staging command: | - STATIC_S3_BASE="s3://cf-apps-static-dev/apps-test-$CIRCLE_SHA1" \ - STATIC_JIRA_S3_BASE="s3://cf-apps-static-dev/apps-test-$CIRCLE_SHA1/jira" \ - STATIC_KLAVIYO_S3_BASE="s3://cf-apps-klaviyo/apps" \ - REACT_APP_BACKEND_BASE_URL=$BACKEND_BASE_URL_TEST \ - REACT_APP_SLACK_CLIENT_ID=$SLACK_CLIENT_ID_TEST \ - STAGE='test' npm run deploy:test + STATIC_S3_BASE="s3://cf-apps-static-dev/apps" \ + STATIC_TEST_S3_BASE="s3://cf-apps-static-dev/apps/apps-test-$CIRCLE_SHA1" \ + STATIC_KLAVIYO_S3_BASE="s3://cf-apps-klaviyo/apps" \ + REACT_APP_BACKEND_BASE_URL=$BACKEND_BASE_URL_TEST \ + REACT_APP_SLACK_CLIENT_ID=$SLACK_CLIENT_ID_TEST \ + STAGE='test' npm run deploy:test + GOOGLE_DOCS_TEST_CLOUDFRONT_DIST_ID=$GOOGLE_DOCS_TEST_CLOUDFRONT_DIST_ID \ - run: name: Invalidate Slack staging cloudfront distribution command: aws cloudfront create-invalidation --distribution-id $SLACK_TEST_CLOUDFRONT_DIST_ID --paths "/*" @@ -154,11 +155,12 @@ jobs: name: Deploy apps to prod command: | STATIC_S3_BASE="s3://cf-apps-static/apps" \ - STATIC_JIRA_S3_BASE="s3://cf-apps-jira" \ - STATIC_KLAVIYO_S3_BASE="s3://cf-apps-klaviyo/apps" \ - REACT_APP_BACKEND_BASE_URL=$APP_SLACK_BACKEND_BASE_URL_PROD \ - REACT_APP_SLACK_CLIENT_ID=$SLACK_CLIENT_ID_PROD \ - STAGE='prd' npm run deploy + STATIC_JIRA_S3_BASE="s3://cf-apps-jira" \ + STATIC_KLAVIYO_S3_BASE="s3://cf-apps-klaviyo/apps" \ + REACT_APP_BACKEND_BASE_URL=$APP_SLACK_BACKEND_BASE_URL_PROD \ + REACT_APP_SLACK_CLIENT_ID=$SLACK_CLIENT_ID_PROD \ + STAGE='prd' npm run deploy + GOOGLE_DOCS_PROD_CLOUDFRONT_DIST_ID=$GOOGLE_DOCS_PROD_CLOUDFRONT_DIST_ID \ - run: name: Post Deploy command: npm run post-deploy diff --git a/apps/google-analytics/package.json b/apps/google-analytics/package.json index 0907a9d1f4..b9814ce0b1 100644 --- a/apps/google-analytics/package.json +++ b/apps/google-analytics/package.json @@ -29,7 +29,7 @@ "start": "cross-env BROWSER=none react-scripts --openssl-legacy-provider start", "build": "react-scripts --openssl-legacy-provider build", "deploy": "aws s3 sync ./build ${STATIC_S3_BASE}/google-analytics --acl public-read", - "deploy:test": "npm run deploy" + "deploy:test": "aws s3 sync ./build ${STATIC_TEST_S3_BASE}/google-analytics --acl public-read" }, "eslintConfig": { "extends": "react-app" diff --git a/apps/google-docs/package.json b/apps/google-docs/package.json index b1be9bb09e..62328404fa 100644 --- a/apps/google-docs/package.json +++ b/apps/google-docs/package.json @@ -28,13 +28,13 @@ "build:functions": "contentful-app-scripts build-functions --ci", "build": "rimraf build && npm run build:frontend && npm run build:functions", "upload:app-dev": "contentful-app-scripts upload --bundle-dir ./build --organization-id 6xdLsz6lCsk0yPOccSsDK7 --definition-id 653vTnuQw3j5onU1tUoH6t --token $CONTENTFUL_ACCESS_TOKEN", - "deploy:oauth-dev": "aws s3 sync ./build ${STATIC_S3_BASE}/apps/google-docs-dev && aws cloudfront create-invalidation --distribution-id $GOOGLE_DOCS_CLOUDFRONT_DIST_ID --paths \"/*\" > /dev/null 2>&1", - "deploy:dev": "npm run build && npm run upload:app-dev && npm run deploy:oauth-dev", + "deploy:sync-dev": "aws s3 sync ./build ${STATIC_S3_BASE}/google-docs-dev && aws cloudfront create-invalidation --distribution-id $GOOGLE_DOCS_TEST_CLOUDFRONT_DIST_ID --paths \"/*\" > /dev/null 2>&1", + "deploy:dev": "npm run build && npm run upload:app-dev && npm run deploy:sync-dev", "upload:app-staging": "contentful-app-scripts upload --bundle-dir ./build --organization-id 6xdLsz6lCsk0yPOccSsDK7 --definition-id 4i0mp5lQtgNsHYVrD5jIkj --token $CONTENTFUL_ACCESS_TOKEN", - "deploy:staging": "npm run build && npm run upload:app-staging && npm run deploy:oauth-dev", - "deploy": "aws s3 sync ./build s3://cf-apps-static/apps/google-docs && aws cloudfront create-invalidation --distribution-id $GOOGLE_DOCS_PROD_CLOUDFRONT_DIST_ID --paths \"/*\" > /dev/null 2>&1", + "deploy:staging": "npm run build && npm run upload:app-staging && npm run deploy:sync-dev", + "deploy:sync-prod": "aws s3 sync ./build ${STATIC_S3_BASE}/google-docs && aws cloudfront create-invalidation --distribution-id $GOOGLE_DOCS_PROD_CLOUDFRONT_DIST_ID --paths \"/*\" > /dev/null 2>&1", "upload": "contentful-app-scripts upload --bundle-dir ./build --organization-id 5EJGHo8tYJcjnEhYWDxivp --definition-id 3EaGZUMKRKVZUyrcoNJ4o4 --token $CONTENTFUL_ACCESS_TOKEN", - "deploy:prod": "npm run build && npm run upload && npm run deploy" + "deploy": "npm run build && npm run upload && npm run deploy:sync-prod" }, "eslintConfig": { "extends": "react-app" diff --git a/apps/jira/jira-app/package.json b/apps/jira/jira-app/package.json index 4a2473a003..ee545366a3 100644 --- a/apps/jira/jira-app/package.json +++ b/apps/jira/jira-app/package.json @@ -34,7 +34,7 @@ "start": "VITE_NGROK_URL=${NGROK_URL} VITE_ATLASSIAN_APP_CLIENT_ID=${ATLASSIAN_APP_CLIENT_ID} vite", "build": "vite build", "deploy": "aws s3 sync ./build ${STATIC_JIRA_S3_BASE} --acl public-read", - "deploy:test": "npm run deploy", + "deploy:test": "aws s3 sync ./build ${STATIC_TEST_S3_BASE}/jira --acl public-read", "test": "vitest", "test:ci": "CI=true vitest" }, diff --git a/apps/slack/frontend/package.json b/apps/slack/frontend/package.json index 02337c8786..a5278faefd 100644 --- a/apps/slack/frontend/package.json +++ b/apps/slack/frontend/package.json @@ -25,7 +25,7 @@ "lint": "eslint --ext .ts,.tsx,.js,.jsx,.svg ./", "upload": "contentful-app-scripts upload --bundle-dir ./build", "deploy": "aws s3 sync ./build ${STATIC_S3_BASE}/slack --acl public-read", - "deploy:test": "aws s3 sync ./build ${STATIC_S3_BASE}/slack-test --acl public-read" + "deploy:test": "aws s3 sync ./build ${STATIC_TEST_S3_BASE}/slack-test --acl public-read" }, "browserslist": { "production": [ From 1b7d992ce386e1fce7cb81213868cd2a0ec91a71 Mon Sep 17 00:00:00 2001 From: Harika Kondur <107296300+harikakondur@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:59:39 -0700 Subject: [PATCH 02/14] feat: preview step + functions refactor [INTEG-3348] (#10356) * wip * simplify agent * organize oauth functions * reorganizing function handlers * reorganizing function handlers (prev `createEntriesfromDocument`) calling each agent in a separate function, + removing plan agent for now * fix: function manifest * minor edit to manifest * replacing single function with 2: one for analyzing ct + one for document processing * fix: add oauth token to analyze content type function * fix: adding entries to document processor o/p * feat: new app action to create entries, preview modal * cleanup + rename preview function * cleanup * chore: delete test docs * cleanup * rename handler for consistency --- apps/google-docs/contentful-app-manifest.json | 50 +- .../{ => agents}/observer/observer.ts | 0 .../functions/createEntriesFromDocument.ts | 90 - .../handlers/analyzeContentTypesHandler.ts | 46 + .../handlers/createEntriesHandler.ts | 51 + .../handlers/createPreviewHandler.ts | 60 + .../functions/{ => oauth}/checkStatus.ts | 0 .../functions/{ => oauth}/completeOauth.ts | 0 .../functions/{ => oauth}/disconnect.ts | 6 +- .../functions/{ => oauth}/initiateOauth.ts | 2 +- .../functions/{types => oauth}/oauth.types.ts | 0 .../page/ContentTypePickerModal.tsx | 2 +- .../src/components/page/ViewPreviewModal.tsx | 180 + .../src/hooks/useDocumentSubmission.ts | 29 +- .../src/hooks/useModalManagement.ts | 12 + apps/google-docs/src/locations/Page.tsx | 106 +- apps/google-docs/src/utils/appActionUtils.ts | 118 + .../google-docs/src/utils/appFunctionUtils.ts | 61 - .../Doc_1_Basic_Structure_Test.json | 2543 ------ .../Doc_2_Rich_Text_Formatting_Test.json | 3474 -------- ...Nested_Structures_And_References_Test.json | 2430 ----- .../Doc_4_Media_Embeds_Test.json | 2040 ----- .../Doc_5_Bulk_Entry_Stress_Test.json | 6435 -------------- .../Doc_6_Multilingual_Test.json | 1875 ---- .../test_docs_json/Doc_7_Edge_Cases_Test.json | 5063 ----------- .../Doc_8_DXP_benefits - Sample.json | 7791 ----------------- .../src/utils/test_docs_json/index.ts | 26 - 27 files changed, 583 insertions(+), 31907 deletions(-) rename apps/google-docs/functions/{ => agents}/observer/observer.ts (100%) delete mode 100644 apps/google-docs/functions/createEntriesFromDocument.ts create mode 100644 apps/google-docs/functions/handlers/analyzeContentTypesHandler.ts create mode 100644 apps/google-docs/functions/handlers/createEntriesHandler.ts create mode 100644 apps/google-docs/functions/handlers/createPreviewHandler.ts rename apps/google-docs/functions/{ => oauth}/checkStatus.ts (100%) rename apps/google-docs/functions/{ => oauth}/completeOauth.ts (100%) rename apps/google-docs/functions/{ => oauth}/disconnect.ts (79%) rename apps/google-docs/functions/{ => oauth}/initiateOauth.ts (97%) rename apps/google-docs/functions/{types => oauth}/oauth.types.ts (100%) create mode 100644 apps/google-docs/src/components/page/ViewPreviewModal.tsx create mode 100644 apps/google-docs/src/utils/appActionUtils.ts delete mode 100644 apps/google-docs/src/utils/appFunctionUtils.ts delete mode 100644 apps/google-docs/src/utils/test_docs_json/Doc_1_Basic_Structure_Test.json delete mode 100644 apps/google-docs/src/utils/test_docs_json/Doc_2_Rich_Text_Formatting_Test.json delete mode 100644 apps/google-docs/src/utils/test_docs_json/Doc_3_Nested_Structures_And_References_Test.json delete mode 100644 apps/google-docs/src/utils/test_docs_json/Doc_4_Media_Embeds_Test.json delete mode 100644 apps/google-docs/src/utils/test_docs_json/Doc_5_Bulk_Entry_Stress_Test.json delete mode 100644 apps/google-docs/src/utils/test_docs_json/Doc_6_Multilingual_Test.json delete mode 100644 apps/google-docs/src/utils/test_docs_json/Doc_7_Edge_Cases_Test.json delete mode 100644 apps/google-docs/src/utils/test_docs_json/Doc_8_DXP_benefits - Sample.json delete mode 100644 apps/google-docs/src/utils/test_docs_json/index.ts diff --git a/apps/google-docs/contentful-app-manifest.json b/apps/google-docs/contentful-app-manifest.json index 1743c85493..424e4c3c0a 100644 --- a/apps/google-docs/contentful-app-manifest.json +++ b/apps/google-docs/contentful-app-manifest.json @@ -22,11 +22,11 @@ }, "functions": [ { - "id": "createEntriesFromDocumentFunction", - "name": "Create content entries from document function", - "description": "Function to create content blocks from App Action.", - "path": "functions/createEntriesFromDocument.js", - "entryFile": "functions/createEntriesFromDocument.ts", + "id": "createPreview", + "name": "Create Preview", + "description": "Parses the Google Doc and creates preview entries", + "path": "functions/handlers/createPreviewHandler.js", + "entryFile": "functions/handlers/createPreviewHandler.ts", "allowNetworks": [ "https://api.openai.com", "*.googleapis.com", @@ -37,12 +37,36 @@ "appaction.call" ] }, + { + "id": "analyzeContentTypes", + "name": "Analyze Content Types", + "description": "Analyzes content type structure and relationships using AI.", + "path": "functions/handlers/analyzeContentTypesHandler.js", + "entryFile": "functions/handlers/analyzeContentTypesHandler.ts", + "allowNetworks": [ + "https://api.openai.com" + ], + "accepts": [ + "appaction.call" + ] + }, + { + "id": "createEntries", + "name": "Create Entries", + "description": "Creates entries in Contentful", + "path": "functions/handlers/createEntriesHandler.js", + "entryFile": "functions/handlers/createEntriesHandler.ts", + "allowNetworks": [], + "accepts": [ + "appaction.call" + ] + }, { "id": "initiateGdocOauth", "name": "Initiate Gdoc OAuth Flow", "description": "Initiates the OAuth flow for Google Docs", - "path": "functions/initiateOauth.js", - "entryFile": "functions/initiateOauth.ts", + "path": "functions/oauth/initiateOauth.js", + "entryFile": "functions/oauth/initiateOauth.ts", "allowNetworks": [ "oauth2.googleapis.com" ], @@ -54,8 +78,8 @@ "id": "completeGdocOauth", "name": "Complete Gdoc OAuth Flow", "description": "Completes the OAuth flow for Google Docs", - "path": "functions/completeOauth.js", - "entryFile": "functions/completeOauth.ts", + "path": "functions/oauth/completeOauth.js", + "entryFile": "functions/oauth/completeOauth.ts", "allowNetworks": [ "oauth2.googleapis.com" ], @@ -67,8 +91,8 @@ "id": "revokeGdocOauthToken", "name": "Revoke Gdoc OAuth Token", "description": "Revoke token for the Google Docs app to disconnect from the app", - "path": "functions/disconnect.js", - "entryFile": "functions/disconnect.ts", + "path": "functions/oauth/disconnect.js", + "entryFile": "functions/oauth/disconnect.ts", "allowNetworks": [ "oauth2.googleapis.com" ], @@ -80,8 +104,8 @@ "id": "checkGdocOauthTokenStatus", "name": "Check Gdoc OAuth Token Status", "description": "Checks the status of the Google Docs OAuth token to see if it is valid", - "path": "functions/checkStatus.js", - "entryFile": "functions/checkStatus.ts", + "path": "functions/oauth/checkStatus.js", + "entryFile": "functions/oauth/checkStatus.ts", "allowNetworks": [ "oauth2.googleapis.com" ], diff --git a/apps/google-docs/functions/observer/observer.ts b/apps/google-docs/functions/agents/observer/observer.ts similarity index 100% rename from apps/google-docs/functions/observer/observer.ts rename to apps/google-docs/functions/agents/observer/observer.ts diff --git a/apps/google-docs/functions/createEntriesFromDocument.ts b/apps/google-docs/functions/createEntriesFromDocument.ts deleted file mode 100644 index 5328c3dd3e..0000000000 --- a/apps/google-docs/functions/createEntriesFromDocument.ts +++ /dev/null @@ -1,90 +0,0 @@ -import type { - FunctionEventContext, - FunctionEventHandler, - FunctionTypeEnum, - AppActionRequest, -} from '@contentful/node-apps-toolkit'; -import { analyzeContentTypes } from './agents/contentTypeParserAgent/contentTypeParser.agent'; -import { analyzeDocumentWithAgent } from './agents/documentParserAgent/documentParser.agent'; -import { fetchContentTypes } from './service/contentTypeService'; -import { initContentfulManagementClient } from './service/initCMAClient'; -import { createEntries } from './service/entryService'; - -export type AppActionParameters = { - contentTypeIds: string[]; - documentId: string; - oauthToken: string; -}; - -interface AppInstallationParameters { - openAiApiKey: string; -} - -/* - * Important Caveat: App Functions have a 30 second limit on execution time. - * There is a likely future where we will need to break down the function into smaller functions. - */ -export const handler: FunctionEventHandler< - FunctionTypeEnum.AppActionCall, - AppActionParameters -> = async ( - event: AppActionRequest<'Custom', AppActionParameters>, - context: FunctionEventContext -) => { - const { contentTypeIds, documentId, oauthToken } = event.body; - const { openAiApiKey } = context.appInstallationParameters as AppInstallationParameters; - - if (!documentId) { - throw new Error('A selected document is required'); - } - - if (!contentTypeIds || contentTypeIds.length === 0) { - throw new Error('At least one content type ID is required'); - } - - const cma = initContentfulManagementClient(context); - const contentTypes = await fetchContentTypes(cma, new Set(contentTypeIds)); - - // Commented out to preserver as much time as possible due to the 30 second limit for App functions - // const contentTypeParserAgentResult = await analyzeContentTypes({ contentTypes, openAiApiKey }); - - // INTEG-3261: Pass the ai content type response to the observer for analysis - // createContentTypeObservationsFromLLMResponse() - - const aiDocumentResponse = await analyzeDocumentWithAgent({ - documentId, - oauthToken, - openAiApiKey, - contentTypes, - }); - - // INTEG-3261: Pass the ai document response to the observer for analysis - // createDocumentObservationsFromLLMResponse() - - // INTEG-3264: Create the entries in Contentful using the entry service - // The aiDocumentResponse.entries is now ready to be passed to the CMA client - const creationResult = await createEntries(cma, aiDocumentResponse.entries, { - spaceId: context.spaceId, - environmentId: context.environmentId, - contentTypes, - }); - console.log('Created Entries Result:', creationResult); - - // INTEG-3265: Create the assets in Contentful using the asset service - // await createAssets() - - return { - success: true, - response: { - // contentTypeParserAgentResult, - summary: aiDocumentResponse.summary, - totalEntriesExtracted: aiDocumentResponse.totalEntries, - createdEntries: creationResult.createdEntries.map((entry) => ({ - id: entry.sys.id, - contentType: entry.sys.contentType.sys.id, - })), - errors: creationResult.errors, - successRate: `${creationResult.createdEntries.length}/${aiDocumentResponse.totalEntries}`, - }, - }; -}; diff --git a/apps/google-docs/functions/handlers/analyzeContentTypesHandler.ts b/apps/google-docs/functions/handlers/analyzeContentTypesHandler.ts new file mode 100644 index 0000000000..456ee64a52 --- /dev/null +++ b/apps/google-docs/functions/handlers/analyzeContentTypesHandler.ts @@ -0,0 +1,46 @@ +import type { + FunctionEventContext, + FunctionEventHandler, + FunctionTypeEnum, + AppActionRequest, +} from '@contentful/node-apps-toolkit'; +import { analyzeContentTypes as analyzeContentTypesAgent } from '../agents/contentTypeParserAgent/contentTypeParser.agent'; +import { fetchContentTypes } from '../service/contentTypeService'; +import { initContentfulManagementClient } from '../service/initCMAClient'; + +export type AnalyzeContentTypesParameters = { + contentTypeIds: string[]; +}; + +interface AppInstallationParameters { + openAiApiKey: string; +} + +export const handler: FunctionEventHandler< + FunctionTypeEnum.AppActionCall, + AnalyzeContentTypesParameters +> = async ( + event: AppActionRequest<'Custom', AnalyzeContentTypesParameters>, + context: FunctionEventContext +) => { + const { contentTypeIds } = event.body; + const { openAiApiKey } = context.appInstallationParameters as AppInstallationParameters; + + if (!contentTypeIds || contentTypeIds.length === 0) { + throw new Error('At least one content type ID is required'); + } + + const cma = initContentfulManagementClient(context); + const contentTypes = await fetchContentTypes(cma, new Set(contentTypeIds)); + + const contentTypeParserAgentResult = await analyzeContentTypesAgent({ + contentTypes, + openAiApiKey, + }); + + console.log('Content type analysis completed', contentTypeParserAgentResult); + return { + success: true, + analysis: contentTypeParserAgentResult, + }; +}; diff --git a/apps/google-docs/functions/handlers/createEntriesHandler.ts b/apps/google-docs/functions/handlers/createEntriesHandler.ts new file mode 100644 index 0000000000..301d3f8079 --- /dev/null +++ b/apps/google-docs/functions/handlers/createEntriesHandler.ts @@ -0,0 +1,51 @@ +import { AppActionRequest, FunctionEventHandler } from '@contentful/node-apps-toolkit'; +import { createEntries } from '../service/entryService'; +import { initContentfulManagementClient } from '../service/initCMAClient'; +import { EntryToCreate } from '../agents/documentParserAgent/schema'; +import { FunctionTypeEnum, FunctionEventContext } from '@contentful/node-apps-toolkit'; + +interface CreateEntriesParameters { + entries: EntryToCreate[]; + contentTypeIds: string[]; +} + +export const handler: FunctionEventHandler< + FunctionTypeEnum.AppActionCall, + CreateEntriesParameters +> = async ( + event: AppActionRequest<'Custom', CreateEntriesParameters>, + context: FunctionEventContext +) => { + const { entries, contentTypeIds } = event.body; + + if (!entries || !Array.isArray(entries) || entries.length === 0) { + throw new Error('entries parameter is required and must be a non-empty array'); + } + + if (!contentTypeIds || !Array.isArray(contentTypeIds) || contentTypeIds.length === 0) { + throw new Error('contentTypeIds parameter is required and must be a non-empty array'); + } + + const cma = initContentfulManagementClient(context); + + // Fetch content types + const contentTypesResponse = await cma.contentType.getMany({}); + const contentTypes = contentTypesResponse.items.filter((ct) => + contentTypeIds.includes(ct.sys.id) + ); + + if (contentTypes.length === 0) { + throw new Error('No matching content types found'); + } + + const result = await createEntries(cma, entries, { + spaceId: context.spaceId, + environmentId: context.environmentId, + contentTypes: contentTypes, + }); + + return { + success: true, + result: result, + }; +}; diff --git a/apps/google-docs/functions/handlers/createPreviewHandler.ts b/apps/google-docs/functions/handlers/createPreviewHandler.ts new file mode 100644 index 0000000000..864e31204e --- /dev/null +++ b/apps/google-docs/functions/handlers/createPreviewHandler.ts @@ -0,0 +1,60 @@ +import type { + FunctionEventContext, + FunctionEventHandler, + FunctionTypeEnum, + AppActionRequest, +} from '@contentful/node-apps-toolkit'; +import { analyzeDocumentWithAgent } from '../agents/documentParserAgent/documentParser.agent'; +import { fetchContentTypes } from '../service/contentTypeService'; +import { initContentfulManagementClient } from '../service/initCMAClient'; + +export type CreatePreviewParameters = { + contentTypeIds: string[]; + documentId: string; + oauthToken: string; +}; + +interface AppInstallationParameters { + openAiApiKey: string; +} + +/** + * Create Preview + * + * Processes a Google Doc and creates preview entries based on the document structure + * and the provided content types. + * + */ +export const handler: FunctionEventHandler< + FunctionTypeEnum.AppActionCall, + CreatePreviewParameters +> = async ( + event: AppActionRequest<'Custom', CreatePreviewParameters>, + context: FunctionEventContext +) => { + const { contentTypeIds, documentId, oauthToken } = event.body; + const { openAiApiKey } = context.appInstallationParameters as AppInstallationParameters; + + if (!contentTypeIds || contentTypeIds.length === 0) { + throw new Error('At least one content type ID is required'); + } + + const cma = initContentfulManagementClient(context); + + const contentTypes = await fetchContentTypes(cma, new Set(contentTypeIds)); + + // Process the document and create preview entries + const aiDocumentResponse = await analyzeDocumentWithAgent({ + documentId, + oauthToken, + openAiApiKey, + contentTypes, + }); + + return { + success: true, + summary: aiDocumentResponse.summary, + totalEntriesExtracted: aiDocumentResponse.totalEntries, + entries: aiDocumentResponse.entries, + }; +}; diff --git a/apps/google-docs/functions/checkStatus.ts b/apps/google-docs/functions/oauth/checkStatus.ts similarity index 100% rename from apps/google-docs/functions/checkStatus.ts rename to apps/google-docs/functions/oauth/checkStatus.ts diff --git a/apps/google-docs/functions/completeOauth.ts b/apps/google-docs/functions/oauth/completeOauth.ts similarity index 100% rename from apps/google-docs/functions/completeOauth.ts rename to apps/google-docs/functions/oauth/completeOauth.ts diff --git a/apps/google-docs/functions/disconnect.ts b/apps/google-docs/functions/oauth/disconnect.ts similarity index 79% rename from apps/google-docs/functions/disconnect.ts rename to apps/google-docs/functions/oauth/disconnect.ts index a7774c86e2..566e9b83e3 100644 --- a/apps/google-docs/functions/disconnect.ts +++ b/apps/google-docs/functions/oauth/disconnect.ts @@ -1,8 +1,4 @@ -import { - AppEventHandlerRequest, - AppEventHandlerResponse, - AppEventContext, -} from './types/oauth.types'; +import { AppEventHandlerRequest, AppEventHandlerResponse, AppEventContext } from './oauth.types'; export const handler = async ( event: AppEventHandlerRequest, diff --git a/apps/google-docs/functions/initiateOauth.ts b/apps/google-docs/functions/oauth/initiateOauth.ts similarity index 97% rename from apps/google-docs/functions/initiateOauth.ts rename to apps/google-docs/functions/oauth/initiateOauth.ts index 425ac24b2a..71c58a5437 100644 --- a/apps/google-docs/functions/initiateOauth.ts +++ b/apps/google-docs/functions/oauth/initiateOauth.ts @@ -6,7 +6,7 @@ import { AppEventHandlerRequest, AppEventContext, AppEventHandlerResponse, -} from './types/oauth.types'; +} from './oauth.types'; export type OAuthSDK = { init: () => Promise; diff --git a/apps/google-docs/functions/types/oauth.types.ts b/apps/google-docs/functions/oauth/oauth.types.ts similarity index 100% rename from apps/google-docs/functions/types/oauth.types.ts rename to apps/google-docs/functions/oauth/oauth.types.ts diff --git a/apps/google-docs/src/components/page/ContentTypePickerModal.tsx b/apps/google-docs/src/components/page/ContentTypePickerModal.tsx index 32d1b34804..b3355e5a5e 100644 --- a/apps/google-docs/src/components/page/ContentTypePickerModal.tsx +++ b/apps/google-docs/src/components/page/ContentTypePickerModal.tsx @@ -177,7 +177,7 @@ export const ContentTypePickerModal = ({ variant="primary" isDisabled={isLoading || isSubmitting} endIcon={isSubmitting ? : undefined}> - Create + Next diff --git a/apps/google-docs/src/components/page/ViewPreviewModal.tsx b/apps/google-docs/src/components/page/ViewPreviewModal.tsx new file mode 100644 index 0000000000..897b5842f2 --- /dev/null +++ b/apps/google-docs/src/components/page/ViewPreviewModal.tsx @@ -0,0 +1,180 @@ +import React, { useMemo } from 'react'; +import { + Box, + Button, + Card, + Flex, + Heading, + Modal, + Paragraph, + Table, + Text, + Badge, +} from '@contentful/f36-components'; +import tokens from '@contentful/f36-tokens'; +import { EntryToCreate } from '../../../functions/agents/documentParserAgent/schema'; + +interface ViewPreviewModalProps { + isOpen: boolean; + onClose: () => void; + entries: EntryToCreate[] | null; + onConfirm: () => void; + isSubmitting: boolean; +} + +export const ViewPreviewModal: React.FC = ({ + isOpen, + onClose, + entries, + onConfirm, + isSubmitting, +}) => { + const entriesByContentType = useMemo(() => { + if (!entries) return {}; + + return entries.reduce((acc, entry) => { + if (!acc[entry.contentTypeId]) { + acc[entry.contentTypeId] = []; + } + acc[entry.contentTypeId].push(entry); + return acc; + }, {} as Record); + }, [entries]); + + const totalEntries = useMemo(() => entries?.length || 0, [entries]); + + const renderFieldValue = (value: any, maxLength: number = 100): string => { + if (value === null || value === undefined) { + return '—'; + } + + if (typeof value === 'object') { + const stringified = JSON.stringify(value, null, 2); + return stringified.length > maxLength + ? stringified.substring(0, maxLength) + '...' + : stringified; + } + + const stringValue = String(value); + return stringValue.length > maxLength + ? stringValue.substring(0, maxLength) + '...' + : stringValue; + }; + + const handleClose = () => { + if (isSubmitting) return; + onClose(); + }; + + const handleConfirm = () => { + if (isSubmitting) return; + onConfirm(); + }; + + if (!entries || entries.length === 0) { + return null; + } + + return ( + + {() => ( + <> + + + + + + + Based off the document, the following entries are being suggested: + + + + + {/* Entries by Content Type */} + {Object.entries(entriesByContentType).map(([contentTypeId, entries], ctIndex) => ( + + + + + {contentTypeId} + + {entries.length} entries + + + {entries.map((entry, entryIndex) => ( + + + + Entry {entryIndex + 1} + + + + + + + Field + Locale + Value + + + + {Object.entries(entry.fields).map(([fieldId, localizedValue]) => { + // localizedValue is a record like { 'en-US': actualValue } + return Object.entries(localizedValue).map(([locale, value]) => ( + + + {fieldId} + + + + {locale} + + + + + {renderFieldValue(value)} + + + + )); + })} + +
+
+
+
+ ))} +
+
+ ))} +
+
+ + + + + + )} +
+ ); +}; diff --git a/apps/google-docs/src/hooks/useDocumentSubmission.ts b/apps/google-docs/src/hooks/useDocumentSubmission.ts index b0ca38061a..345678077d 100644 --- a/apps/google-docs/src/hooks/useDocumentSubmission.ts +++ b/apps/google-docs/src/hooks/useDocumentSubmission.ts @@ -1,11 +1,12 @@ import { useState, useCallback } from 'react'; import { PageAppSDK } from '@contentful/app-sdk'; -import { createEntriesFromDocumentAction } from '../utils/appFunctionUtils'; -import { ERROR_MESSAGES, SUCCESS_MESSAGES } from '../constants/messages'; +import { analyzeContentTypesAction, createPreviewAction } from '../utils/appActionUtils'; +import { ERROR_MESSAGES } from '../constants/messages'; +import { EntryToCreate } from '../../functions/agents/documentParserAgent/schema'; interface UseDocumentSubmissionReturn { isSubmitting: boolean; - result: any; + previewEntries: EntryToCreate[]; errorMessage: string | null; successMessage: string | null; submit: (contentTypeIds: string[]) => Promise; @@ -18,7 +19,7 @@ export const useDocumentSubmission = ( oauthToken: string ): UseDocumentSubmissionReturn => { const [isSubmitting, setIsSubmitting] = useState(false); - const [result, setResult] = useState(null); + const [previewEntries, setPreviewEntries] = useState([]); const [errorMessage, setErrorMessage] = useState(null); const [successMessage, setSuccessMessage] = useState(null); @@ -55,17 +56,25 @@ export const useDocumentSubmission = ( setIsSubmitting(true); setErrorMessage(null); setSuccessMessage(null); - setResult(null); + setPreviewEntries([]); try { - const response = await createEntriesFromDocumentAction( + const analyzeContentTypesResponse = await analyzeContentTypesAction( + sdk, + contentTypeIds, + oauthToken + ); + console.log('analyzeContentTypesResponse', analyzeContentTypesResponse); + + const processDocumentResponse = await createPreviewAction( sdk, contentTypeIds, documentId, oauthToken ); - setResult(response); - setSuccessMessage(SUCCESS_MESSAGES.ENTRIES_CREATED); + console.log('processDocumentResponse', processDocumentResponse); + + setPreviewEntries((processDocumentResponse as any).sys.result.entries); } catch (error) { setErrorMessage(error instanceof Error ? error.message : ERROR_MESSAGES.SUBMISSION_FAILED); } finally { @@ -76,14 +85,14 @@ export const useDocumentSubmission = ( ); const clearMessages = useCallback(() => { - setResult(null); + setPreviewEntries([]); setSuccessMessage(null); setErrorMessage(null); }, []); return { isSubmitting, - result, + previewEntries, errorMessage, successMessage, submit, diff --git a/apps/google-docs/src/hooks/useModalManagement.ts b/apps/google-docs/src/hooks/useModalManagement.ts index 91a1062aac..a06b0d95a8 100644 --- a/apps/google-docs/src/hooks/useModalManagement.ts +++ b/apps/google-docs/src/hooks/useModalManagement.ts @@ -4,24 +4,28 @@ interface ModalStates { isUploadModalOpen: boolean; isContentTypePickerOpen: boolean; isConfirmCancelModalOpen: boolean; + isPreviewModalOpen: boolean; } interface ModalSetters { setIsUploadModalOpen: (value: boolean) => void; setIsContentTypePickerOpen: (value: boolean) => void; setIsConfirmCancelModalOpen: (value: boolean) => void; + setIsPreviewModalOpen: (value: boolean) => void; } export enum ModalType { UPLOAD = 'upload', CONTENT_TYPE_PICKER = 'contentTypePicker', CONFIRM_CANCEL = 'confirmCancel', + PREVIEW = 'preview', } export const useModalManagement = () => { const [isUploadModalOpen, setIsUploadModalOpen] = useState(false); const [isContentTypePickerOpen, setIsContentTypePickerOpen] = useState(false); const [isConfirmCancelModalOpen, setIsConfirmCancelModalOpen] = useState(false); + const [isPreviewModalOpen, setIsPreviewModalOpen] = useState(false); const openModal = (modalType: ModalType) => { switch (modalType) { @@ -34,6 +38,9 @@ export const useModalManagement = () => { case ModalType.CONFIRM_CANCEL: setIsConfirmCancelModalOpen(true); break; + case ModalType.PREVIEW: + setIsPreviewModalOpen(true); + break; } }; @@ -48,6 +55,9 @@ export const useModalManagement = () => { case ModalType.CONFIRM_CANCEL: setIsConfirmCancelModalOpen(false); break; + case ModalType.PREVIEW: + setIsPreviewModalOpen(false); + break; } }; @@ -56,11 +66,13 @@ export const useModalManagement = () => { isUploadModalOpen, isContentTypePickerOpen, isConfirmCancelModalOpen, + isPreviewModalOpen, } as ModalStates, setModalStates: { setIsUploadModalOpen, setIsContentTypePickerOpen, setIsConfirmCancelModalOpen, + setIsPreviewModalOpen, } as ModalSetters, openModal, closeModal, diff --git a/apps/google-docs/src/locations/Page.tsx b/apps/google-docs/src/locations/Page.tsx index bfbc73e3c2..7128b8da98 100644 --- a/apps/google-docs/src/locations/Page.tsx +++ b/apps/google-docs/src/locations/Page.tsx @@ -12,11 +12,14 @@ import { useModalManagement, ModalType } from '../hooks/useModalManagement'; import { useProgressTracking } from '../hooks/useProgressTracking'; import { useDocumentSubmission } from '../hooks/useDocumentSubmission'; import SelectDocumentModal from '../components/page/SelectDocumentModal'; +import { ViewPreviewModal } from '../components/page/ViewPreviewModal'; +import { createEntriesAction } from '../utils/appActionUtils'; const Page = () => { const sdk = useSDK(); const { modalStates, openModal, closeModal } = useModalManagement(); const [oauthToken, setOauthToken] = useState(''); + const [isCreatingEntries, setIsCreatingEntries] = useState(false); const { hasStarted, setHasStarted, @@ -29,8 +32,11 @@ const Page = () => { pendingCloseAction, setPendingCloseAction, } = useProgressTracking(); - const { result, successMessage, errorMessage, submit, clearMessages, isSubmitting } = - useDocumentSubmission(sdk, documentId, oauthToken); + const { previewEntries, submit, clearMessages, isSubmitting } = useDocumentSubmission( + sdk, + documentId, + oauthToken + ); // Track previous submission state to detect completion const prevIsSubmittingRef = useRef(false); @@ -110,20 +116,58 @@ const Page = () => { `Selected ${contentTypes.length} content type${contentTypes.length > 1 ? 's' : ''}: ${names}` ); - // Call create entries function after content types are selected await submit(ids); }; - // Close the ContentTypePickerModal when submission completes + const handlePreviewModalConfirm = async (contentTypes: SelectedContentType[]) => { + if (!previewEntries || previewEntries.length === 0) { + sdk.notifier.error('No entries to create'); + return; + } + + setIsCreatingEntries(true); + try { + const ids = contentTypes.map((ct) => ct.id); + const entryResult: any = await createEntriesAction(sdk, previewEntries, ids); + + if (entryResult.errorCount > 0) { + sdk.notifier.warning( + `Created ${entryResult.createdCount} entries with ${entryResult.errorCount} errors` + ); + console.error('Entry creation errors:', entryResult.errors); + } else { + sdk.notifier.success(`Successfully created ${entryResult.createdCount} entries`); + } + + // Close the preview modal and reset progress after creating entries + closeModal(ModalType.PREVIEW); + resetProgress(); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; + sdk.notifier.error(`Failed to create entries: ${errorMessage}`); + console.error('Entry creation failed:', error); + } finally { + setIsCreatingEntries(false); + } + }; + + // Close the ContentTypePickerModal when submission completes and open preview modal useEffect(() => { const submissionJustCompleted = prevIsSubmittingRef.current && !isSubmitting; if (submissionJustCompleted && modalStates.isContentTypePickerOpen) { + console.log('Document processing completed, previewEntries:', previewEntries); closeModal(ModalType.CONTENT_TYPE_PICKER); + + // Open preview modal if we have entries + if (previewEntries && previewEntries.length > 0) { + console.log('Opening preview modal with', previewEntries.length, 'entries'); + openModal(ModalType.PREVIEW); + } } prevIsSubmittingRef.current = isSubmitting; - }, [isSubmitting, modalStates.isContentTypePickerOpen, closeModal]); + }, [isSubmitting, modalStates.isContentTypePickerOpen, closeModal, openModal, previewEntries]); // Show getting started page if not started yet if (!hasStarted) { @@ -158,50 +202,14 @@ const Page = () => { onConfirm={handleConfirmCancel} onCancel={handleKeepCreating} /> - {(result || successMessage || errorMessage) && ( - - - - - {successMessage && {successMessage}} - {errorMessage && {errorMessage}} - - {result && ( - - - Response - - - {JSON.stringify(result, null, 2)} - - - )} - - - - - - - )} + + closeModal(ModalType.PREVIEW)} + entries={previewEntries} + onConfirm={() => handlePreviewModalConfirm(selectedContentTypes)} + isSubmitting={isCreatingEntries} + /> ); }; diff --git a/apps/google-docs/src/utils/appActionUtils.ts b/apps/google-docs/src/utils/appActionUtils.ts new file mode 100644 index 0000000000..118426665e --- /dev/null +++ b/apps/google-docs/src/utils/appActionUtils.ts @@ -0,0 +1,118 @@ +import { PageAppSDK, ConfigAppSDK } from '@contentful/app-sdk'; +import { EntryToCreate } from '../../functions/agents/documentParserAgent/schema'; + +/** + * Fetches the app action ID by name from the current environment + * @param sdk - The Contentful SDK instance + * @param actionName - The name of the app action to find + * @returns The app action ID + * @throws Error if the app action is not found + */ +export async function getAppActionId( + sdk: PageAppSDK | ConfigAppSDK, + actionName: string +): Promise { + const appActions = await sdk.cma.appAction.getManyForEnvironment({ + environmentId: sdk.ids.environment, + spaceId: sdk.ids.space, + }); + const appAction = appActions.items.find((action) => action.name === actionName); + if (!appAction) { + throw new Error(`App action "${actionName}" not found`); + } + + return appAction.sys.id; +} + +/** + * Generic helper to call an app action with parameters + * @param sdk - The Contentful SDK instance + * @param actionName - The name of the app action to call + * @param parameters - The parameters to pass to the app action + * @returns The result from the app action + * @throws Error if the app action fails + */ +async function callAppAction( + sdk: PageAppSDK | ConfigAppSDK, + actionName: string, + parameters: Record +): Promise { + try { + const appDefinitionId = sdk.ids.app; + + if (!appDefinitionId) { + throw new Error('App definition ID not found'); + } + + const appActionId = await getAppActionId(sdk, actionName); + const result = await sdk.cma.appActionCall.createWithResult( + { + appDefinitionId, + appActionId, + }, + { + parameters, + } + ); + + if ('errors' in result && result.errors) { + throw new Error(JSON.stringify(result.errors)); + } + + return result as T; + } catch (error) { + console.error(`Error calling app action "${actionName}"`, error); + throw new Error( + error instanceof Error ? error.message : `Failed to call app action "${actionName}"` + ); + } +} + +/** + * Analyzes content type structure and relationships using AI + * @param sdk - The Contentful SDK instance + * @param contentTypeIds - Array of content type IDs to analyze + * @returns Analysis result from the app action + */ +export const analyzeContentTypesAction = async ( + sdk: PageAppSDK | ConfigAppSDK, + contentTypeIds: string[], + oauthToken: string +) => { + return callAppAction(sdk, 'analyzeContentTypes', { contentTypeIds, oauthToken }); +}; + +/** + * Processes a document and creates Contentful entries + * @param sdk - The Contentful SDK instance + * @param contentTypeIds - Array of content type IDs to use for entry creation + * @param document - The document to process (JSON object or string) + * @returns Processing result from the app action + */ +export const createPreviewAction = async ( + sdk: PageAppSDK | ConfigAppSDK, + contentTypeIds: string[], + documentId: string, + oauthToken: string +) => { + return callAppAction(sdk, 'createPreview', { + contentTypeIds, + documentId, + oauthToken, + }); +}; + +/** + * Creates entries in Contentful from parsed document data + * @param sdk - The Contentful SDK instance + * @param entries - Array of entries to create + * @param contentTypeIds - Array of content type IDs used in the entries + * @returns Creation result with created entries and errors + */ +export const createEntriesAction = async ( + sdk: PageAppSDK | ConfigAppSDK, + entries: EntryToCreate[], + contentTypeIds: string[] +) => { + return callAppAction(sdk, 'createEntries', { entries, contentTypeIds }); +}; diff --git a/apps/google-docs/src/utils/appFunctionUtils.ts b/apps/google-docs/src/utils/appFunctionUtils.ts deleted file mode 100644 index 0b11be6a22..0000000000 --- a/apps/google-docs/src/utils/appFunctionUtils.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { PageAppSDK, ConfigAppSDK } from '@contentful/app-sdk'; - -/** - * Fetches the app action ID by name from the current environment - * @param sdk - The Contentful SDK instance - * @param actionName - The name of the app action to find - * @returns The app action ID - * @throws Error if the app action is not found - */ -export async function getAppActionId( - sdk: PageAppSDK | ConfigAppSDK, - actionName: string -): Promise { - const appActions = await sdk.cma.appAction.getManyForEnvironment({ - environmentId: sdk.ids.environment, - spaceId: sdk.ids.space, - }); - const appAction = appActions.items.find((action) => action.name === actionName); - if (!appAction) { - throw new Error(`App action "${actionName}" not found`); - } - - return appAction.sys.id; -} - -export const createEntriesFromDocumentAction = async ( - sdk: PageAppSDK | ConfigAppSDK, - contentTypeIds: string[], - documentId: string, - oauthToken: string -) => { - try { - const appDefinitionId = sdk.ids.app; - - if (!appDefinitionId) { - throw new Error('App definition ID not found'); - } - - const appActionId = await getAppActionId(sdk, 'createEntriesFromDocumentAction'); - const result = await sdk.cma.appActionCall.createWithResult( - { - appDefinitionId, - appActionId, - }, - { - parameters: { contentTypeIds, documentId, oauthToken }, - } - ); - - if ('errors' in result && result.errors) { - throw new Error(JSON.stringify(result.errors)); - } - - return result; - } catch (error) { - console.error('Error creating entries from document', error); - throw new Error( - error instanceof Error ? error.message : 'Failed to create entries from document' - ); - } -}; diff --git a/apps/google-docs/src/utils/test_docs_json/Doc_1_Basic_Structure_Test.json b/apps/google-docs/src/utils/test_docs_json/Doc_1_Basic_Structure_Test.json deleted file mode 100644 index ccb5969cbf..0000000000 --- a/apps/google-docs/src/utils/test_docs_json/Doc_1_Basic_Structure_Test.json +++ /dev/null @@ -1,2543 +0,0 @@ -{ - "documentId": "12ohHh1SR47s-hm2lRtwK0NY-cRi_Nc6coAIrb9vylOM", - "suggestionsViewMode": "PREVIEW_WITHOUT_SUGGESTIONS", - "tabs": [ - { - "documentTab": { - "body": { - "content": [ - { - "endIndex": 1, - "sectionBreak": { - "sectionStyle": { - "columnSeparatorStyle": "NONE", - "contentDirection": "LEFT_TO_RIGHT", - "sectionType": "CONTINUOUS" - } - } - }, - { - "endIndex": 40, - "paragraph": { - "elements": [ - { - "endIndex": 40, - "startIndex": 1, - "textRun": { - "content": "Document Import \u2014 Basic Structure Test\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_1" - } - }, - "startIndex": 1 - }, - { - "endIndex": 49, - "paragraph": { - "elements": [ - { - "endIndex": 49, - "startIndex": 40, - "textRun": { - "content": "Overview\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 40 - }, - { - "endIndex": 312, - "paragraph": { - "elements": [ - { - "endIndex": 312, - "startIndex": 49, - "textRun": { - "content": "This document is designed to validate that the document importer correctly identifies and parses basic content structures including headings, paragraphs, and list items. The expected result is that all elements are represented as structured fields in Contentful.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 49 - }, - { - "endIndex": 329, - "paragraph": { - "elements": [ - { - "endIndex": 329, - "startIndex": 312, - "textRun": { - "content": "Headings Example\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 312 - }, - { - "endIndex": 422, - "paragraph": { - "elements": [ - { - "endIndex": 422, - "startIndex": 329, - "textRun": { - "content": "Below is a nested heading example to ensure the importer recognizes hierarchical structures.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 329 - }, - { - "endIndex": 441, - "paragraph": { - "elements": [ - { - "endIndex": 441, - "startIndex": 422, - "textRun": { - "content": "Subsection Example\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_3" - } - }, - "startIndex": 422 - }, - { - "endIndex": 502, - "paragraph": { - "elements": [ - { - "endIndex": 502, - "startIndex": 441, - "textRun": { - "content": "This subsection demonstrates heading depth beyond level two.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 441 - }, - { - "endIndex": 521, - "paragraph": { - "elements": [ - { - "endIndex": 521, - "startIndex": 502, - "textRun": { - "content": "Paragraph Examples\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 502 - }, - { - "endIndex": 615, - "paragraph": { - "elements": [ - { - "endIndex": 615, - "startIndex": 521, - "textRun": { - "content": "This is a normal paragraph with a few sentences. The quick brown fox jumps over the lazy dog.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 521 - }, - { - "endIndex": 743, - "paragraph": { - "elements": [ - { - "endIndex": 743, - "startIndex": 615, - "textRun": { - "content": "This is another paragraph meant to simulate multiple blocks of text. Each should appear as a separate content node when parsed.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 615 - }, - { - "endIndex": 765, - "paragraph": { - "elements": [ - { - "endIndex": 765, - "startIndex": 743, - "textRun": { - "content": "Bulleted List Example\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 743 - }, - { - "endIndex": 772, - "paragraph": { - "bullet": { - "listId": "kix.list.1", - "textStyle": {} - }, - "elements": [ - { - "endIndex": 772, - "startIndex": 765, - "textRun": { - "content": "Apples\n", - "textStyle": { - "backgroundColor": {}, - "baselineOffset": "NONE", - "bold": false, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "italic": false, - "smallCaps": false, - "strikethrough": false, - "underline": false, - "weightedFontFamily": { - "fontFamily": "Cambria", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": true, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 115, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "spacingMode": "NEVER_COLLAPSE" - } - }, - "startIndex": 765 - }, - { - "endIndex": 780, - "paragraph": { - "bullet": { - "listId": "kix.list.1", - "textStyle": {} - }, - "elements": [ - { - "endIndex": 780, - "startIndex": 772, - "textRun": { - "content": "Bananas\n", - "textStyle": { - "backgroundColor": {}, - "baselineOffset": "NONE", - "bold": false, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "italic": false, - "smallCaps": false, - "strikethrough": false, - "underline": false, - "weightedFontFamily": { - "fontFamily": "Cambria", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": true, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 115, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "spacingMode": "NEVER_COLLAPSE" - } - }, - "startIndex": 772 - }, - { - "endIndex": 789, - "paragraph": { - "bullet": { - "listId": "kix.list.1", - "textStyle": {} - }, - "elements": [ - { - "endIndex": 789, - "startIndex": 780, - "textRun": { - "content": "Cherries\n", - "textStyle": { - "backgroundColor": {}, - "baselineOffset": "NONE", - "bold": false, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "italic": false, - "smallCaps": false, - "strikethrough": false, - "underline": false, - "weightedFontFamily": { - "fontFamily": "Cambria", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": true, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 115, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 10, - "unit": "PT" - }, - "spacingMode": "NEVER_COLLAPSE" - } - }, - "startIndex": 780 - }, - { - "endIndex": 811, - "paragraph": { - "elements": [ - { - "endIndex": 811, - "startIndex": 789, - "textRun": { - "content": "Numbered List Example\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 789 - }, - { - "endIndex": 845, - "paragraph": { - "bullet": { - "listId": "kix.list.5", - "textStyle": {} - }, - "elements": [ - { - "endIndex": 845, - "startIndex": 811, - "textRun": { - "content": "Step One \u2014 Initialize the import.\n", - "textStyle": { - "backgroundColor": {}, - "baselineOffset": "NONE", - "bold": false, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "italic": false, - "smallCaps": false, - "strikethrough": false, - "underline": false, - "weightedFontFamily": { - "fontFamily": "Cambria", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": true, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 115, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "spacingMode": "NEVER_COLLAPSE" - } - }, - "startIndex": 811 - }, - { - "endIndex": 886, - "paragraph": { - "bullet": { - "listId": "kix.list.5", - "textStyle": {} - }, - "elements": [ - { - "endIndex": 886, - "startIndex": 845, - "textRun": { - "content": "Step Two \u2014 Parse the document structure.\n", - "textStyle": { - "backgroundColor": {}, - "baselineOffset": "NONE", - "bold": false, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "italic": false, - "smallCaps": false, - "strikethrough": false, - "underline": false, - "weightedFontFamily": { - "fontFamily": "Cambria", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": true, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 115, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "spacingMode": "NEVER_COLLAPSE" - } - }, - "startIndex": 845 - }, - { - "endIndex": 931, - "paragraph": { - "bullet": { - "listId": "kix.list.5", - "textStyle": {} - }, - "elements": [ - { - "endIndex": 931, - "startIndex": 886, - "textRun": { - "content": "Step Three \u2014 Validate results in Contentful.\n", - "textStyle": { - "backgroundColor": {}, - "baselineOffset": "NONE", - "bold": false, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "italic": false, - "smallCaps": false, - "strikethrough": false, - "underline": false, - "weightedFontFamily": { - "fontFamily": "Cambria", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": true, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 115, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 10, - "unit": "PT" - }, - "spacingMode": "NEVER_COLLAPSE" - } - }, - "startIndex": 886 - }, - { - "endIndex": 948, - "paragraph": { - "elements": [ - { - "endIndex": 948, - "startIndex": 931, - "textRun": { - "content": "Expected Outcome\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 931 - }, - { - "endIndex": 1102, - "paragraph": { - "elements": [ - { - "endIndex": 1102, - "startIndex": 948, - "textRun": { - "content": "Each heading, paragraph, and list item should be recognized and transformed into appropriate structured content fields without losing order or hierarchy.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 948 - } - ] - }, - "documentStyle": { - "background": { - "color": {} - }, - "documentFormat": { - "documentMode": "PAGES" - }, - "marginBottom": { - "magnitude": 72, - "unit": "PT" - }, - "marginFooter": { - "magnitude": 36, - "unit": "PT" - }, - "marginHeader": { - "magnitude": 36, - "unit": "PT" - }, - "marginLeft": { - "magnitude": 90, - "unit": "PT" - }, - "marginRight": { - "magnitude": 90, - "unit": "PT" - }, - "marginTop": { - "magnitude": 72, - "unit": "PT" - }, - "pageNumberStart": 1, - "pageSize": { - "height": { - "magnitude": 792, - "unit": "PT" - }, - "width": { - "magnitude": 612, - "unit": "PT" - } - }, - "useCustomHeaderFooterMargins": true - }, - "lists": { - "kix.list.1": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Noto Sans Symbols", - "weight": 400 - } - } - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.2": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Noto Sans Symbols", - "weight": 400 - } - } - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.3": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 54, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Noto Sans Symbols", - "weight": 400 - } - } - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.4": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Noto Sans Symbols", - "weight": 400 - } - } - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.5": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.6": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.7": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 54, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.8": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.9": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "magnitude": 72, - "unit": "PT" - }, - "indentStart": { - "magnitude": 90, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - } - }, - "namedStyles": { - "styles": [ - { - "namedStyleType": "NORMAL_TEXT", - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": true, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 115, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 10, - "unit": "PT" - }, - "spacingMode": "NEVER_COLLAPSE" - }, - "textStyle": { - "backgroundColor": {}, - "baselineOffset": "NONE", - "bold": false, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "italic": false, - "smallCaps": false, - "strikethrough": false, - "underline": false, - "weightedFontFamily": { - "fontFamily": "Cambria", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_1", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 24, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "bold": true, - "fontSize": { - "magnitude": 14, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.5686275, - "green": 0.3764706, - "red": 0.21176471 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_2", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "bold": true, - "fontSize": { - "magnitude": 13, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_3", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "bold": true, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_4", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "bold": true, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "italic": true, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_5", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.38039216, - "green": 0.24705882, - "red": 0.14117648 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_6", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.38039216, - "green": 0.24705882, - "red": 0.14117648 - } - } - }, - "italic": true, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "TITLE", - "paragraphStyle": { - "borderBottom": { - "color": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "dashStyle": "SOLID", - "padding": { - "magnitude": 4, - "unit": "PT" - }, - "width": { - "magnitude": 1, - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT", - "spaceBelow": { - "magnitude": 15, - "unit": "PT" - }, - "spacingMode": "NEVER_COLLAPSE" - }, - "textStyle": { - "fontSize": { - "magnitude": 26, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.3647059, - "green": 0.21176471, - "red": 0.09019608 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "SUBTITLE", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - }, - "textStyle": { - "fontSize": { - "magnitude": 12, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "italic": true, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - } - ] - } - }, - "tabProperties": { - "index": 0, - "tabId": "t.0", - "title": "Tab 1" - } - } - ], - "title": "Doc_1_Basic_Structure_Test" -} \ No newline at end of file diff --git a/apps/google-docs/src/utils/test_docs_json/Doc_2_Rich_Text_Formatting_Test.json b/apps/google-docs/src/utils/test_docs_json/Doc_2_Rich_Text_Formatting_Test.json deleted file mode 100644 index dda05c28b4..0000000000 --- a/apps/google-docs/src/utils/test_docs_json/Doc_2_Rich_Text_Formatting_Test.json +++ /dev/null @@ -1,3474 +0,0 @@ -{ - "documentId": "1Ufzr1TNu5SPEbrKHqPW_GA7iTH0A6jqB2FH5vaIHfcg", - "revisionId": "AOuX7-bW88EH4C0h-4f72CtHsTHW3qibgy7XhqIc72dtpwcXOu4OML5YwdE6x1QPwqy90OMMtCceqJE0i4IPSw", - "suggestionsViewMode": "SUGGESTIONS_INLINE", - "tabs": [ - { - "documentTab": { - "body": { - "content": [ - { - "endIndex": 1, - "sectionBreak": { - "sectionStyle": { - "columnSeparatorStyle": "NONE", - "contentDirection": "LEFT_TO_RIGHT", - "sectionType": "CONTINUOUS" - } - } - }, - { - "endIndex": 45, - "paragraph": { - "elements": [ - { - "endIndex": 45, - "startIndex": 1, - "textRun": { - "content": "Document Import \u2014 Rich Text Formatting Test\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_1" - } - }, - "startIndex": 1 - }, - { - "endIndex": 54, - "paragraph": { - "elements": [ - { - "endIndex": 54, - "startIndex": 45, - "textRun": { - "content": "Overview\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 45 - }, - { - "endIndex": 327, - "paragraph": { - "elements": [ - { - "endIndex": 327, - "startIndex": 54, - "textRun": { - "content": "This document is designed to validate that the document importer accurately preserves and represents rich text formatting such as bold, italic, underline, and combinations thereof. The goal is to ensure all style data is correctly parsed into the structured content model.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 54 - }, - { - "endIndex": 347, - "paragraph": { - "elements": [ - { - "endIndex": 347, - "startIndex": 327, - "textRun": { - "content": "Formatting Examples\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 327 - }, - { - "endIndex": 413, - "paragraph": { - "elements": [ - { - "endIndex": 371, - "startIndex": 347, - "textRun": { - "content": "This sentence contains \u000b", - "textStyle": {} - } - }, - { - "endIndex": 375, - "startIndex": 371, - "textRun": { - "content": "bold", - "textStyle": { - "bold": true - } - } - }, - { - "endIndex": 377, - "startIndex": 375, - "textRun": { - "content": ", ", - "textStyle": {} - } - }, - { - "endIndex": 383, - "startIndex": 377, - "textRun": { - "content": "italic", - "textStyle": { - "italic": true - } - } - }, - { - "endIndex": 389, - "startIndex": 383, - "textRun": { - "content": ", and ", - "textStyle": {} - } - }, - { - "endIndex": 399, - "startIndex": 389, - "textRun": { - "content": "underlined", - "textStyle": { - "underline": true - } - } - }, - { - "endIndex": 413, - "startIndex": 399, - "textRun": { - "content": " text styles.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 347 - }, - { - "endIndex": 538, - "paragraph": { - "elements": [ - { - "endIndex": 538, - "startIndex": 413, - "textRun": { - "content": "The importer should retain these inline formatting features within paragraph fields, including nested or overlapping styles.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 413 - }, - { - "endIndex": 567, - "paragraph": { - "elements": [ - { - "endIndex": 567, - "startIndex": 538, - "textRun": { - "content": "Combined Formatting Examples\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 538 - }, - { - "endIndex": 631, - "paragraph": { - "elements": [ - { - "endIndex": 597, - "startIndex": 567, - "textRun": { - "content": "This text is bold and italic. ", - "textStyle": { - "bold": true, - "italic": true - } - } - }, - { - "endIndex": 630, - "startIndex": 597, - "textRun": { - "content": "This text is underlined and bold.", - "textStyle": { - "bold": true, - "underline": true - } - } - }, - { - "endIndex": 631, - "startIndex": 630, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 567 - }, - { - "endIndex": 655, - "paragraph": { - "elements": [ - { - "endIndex": 655, - "startIndex": 631, - "textRun": { - "content": "Mixed Content Paragraph\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 631 - }, - { - "endIndex": 759, - "paragraph": { - "elements": [ - { - "endIndex": 697, - "startIndex": 655, - "textRun": { - "content": "This paragraph contains mixed formatting: ", - "textStyle": {} - } - }, - { - "endIndex": 710, - "startIndex": 697, - "textRun": { - "content": "normal text, ", - "textStyle": { - "italic": false - } - } - }, - { - "endIndex": 724, - "startIndex": 710, - "textRun": { - "content": "italic words, ", - "textStyle": { - "italic": true - } - } - }, - { - "endIndex": 738, - "startIndex": 724, - "textRun": { - "content": "bold phrases, ", - "textStyle": { - "bold": true - } - } - }, - { - "endIndex": 759, - "startIndex": 738, - "textRun": { - "content": "underlined sections.\n", - "textStyle": { - "bold": false, - "italic": false, - "underline": true - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 655 - }, - { - "endIndex": 787, - "paragraph": { - "elements": [ - { - "endIndex": 787, - "startIndex": 759, - "textRun": { - "content": "Further Formatting Examples\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "headingId": "h.8j7x8f9gg5xs", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 759 - }, - { - "endIndex": 827, - "paragraph": { - "bullet": { - "listId": "kix.fwj8bl2edzyz", - "textStyle": { - "backgroundColor": {}, - "baselineOffset": "NONE", - "bold": false, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "italic": false, - "smallCaps": false, - "strikethrough": false, - "underline": true, - "weightedFontFamily": { - "fontFamily": "Cambria", - "weight": 400 - } - } - }, - "elements": [ - { - "endIndex": 808, - "startIndex": 787, - "textRun": { - "content": "This text includes a ", - "textStyle": {} - } - }, - { - "endIndex": 825, - "startIndex": 808, - "textRun": { - "content": "hyperlink example", - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.8, - "green": 0.33333334, - "red": 0.06666667 - } - } - }, - "link": { - "url": "https://www.google.com/search?q=https://www.example.com" - }, - "underline": true - } - } - }, - { - "endIndex": 827, - "startIndex": 825, - "textRun": { - "content": ".\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 787 - }, - { - "endIndex": 862, - "paragraph": { - "bullet": { - "listId": "kix.fwj8bl2edzyz", - "textStyle": { - "backgroundColor": {}, - "baselineOffset": "NONE", - "bold": false, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "italic": false, - "smallCaps": false, - "strikethrough": false, - "underline": true, - "weightedFontFamily": { - "fontFamily": "Cambria", - "weight": 400 - } - } - }, - "elements": [ - { - "endIndex": 849, - "startIndex": 827, - "textRun": { - "content": "This is an example of ", - "textStyle": {} - } - }, - { - "endIndex": 860, - "startIndex": 849, - "textRun": { - "content": "inline code", - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.21960784, - "green": 0.5019608, - "red": 0.09411765 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Roboto Mono", - "weight": 400 - } - } - } - }, - { - "endIndex": 862, - "startIndex": 860, - "textRun": { - "content": ".\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 827 - }, - { - "endIndex": 863, - "paragraph": { - "elements": [ - { - "endIndex": 863, - "startIndex": 862, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": true, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 115, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 10, - "unit": "PT" - }, - "spacingMode": "NEVER_COLLAPSE" - } - }, - "startIndex": 862 - }, - { - "endIndex": 896, - "paragraph": { - "elements": [ - { - "endIndex": 864, - "startIndex": 863, - "textRun": { - "content": "\ue907", - "textStyle": {} - } - }, - { - "endIndex": 866, - "startIndex": 864, - "textRun": { - "content": "//", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.21960784, - "green": 0.5019608, - "red": 0.09411765 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Roboto Mono", - "weight": 400 - } - } - } - }, - { - "endIndex": 867, - "startIndex": 866, - "textRun": { - "content": " ", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.44705883, - "green": 0.023529412, - "red": 0.72156864 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Roboto Mono", - "weight": 400 - } - } - } - }, - { - "endIndex": 871, - "startIndex": 867, - "textRun": { - "content": "This", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.21960784, - "green": 0.5019608, - "red": 0.09411765 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Roboto Mono", - "weight": 400 - } - } - } - }, - { - "endIndex": 872, - "startIndex": 871, - "textRun": { - "content": " ", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.44705883, - "green": 0.023529412, - "red": 0.72156864 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Roboto Mono", - "weight": 400 - } - } - } - }, - { - "endIndex": 874, - "startIndex": 872, - "textRun": { - "content": "is", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.21960784, - "green": 0.5019608, - "red": 0.09411765 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Roboto Mono", - "weight": 400 - } - } - } - }, - { - "endIndex": 875, - "startIndex": 874, - "textRun": { - "content": " ", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.44705883, - "green": 0.023529412, - "red": 0.72156864 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Roboto Mono", - "weight": 400 - } - } - } - }, - { - "endIndex": 876, - "startIndex": 875, - "textRun": { - "content": "a", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.21960784, - "green": 0.5019608, - "red": 0.09411765 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Roboto Mono", - "weight": 400 - } - } - } - }, - { - "endIndex": 877, - "startIndex": 876, - "textRun": { - "content": " ", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.44705883, - "green": 0.023529412, - "red": 0.72156864 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Roboto Mono", - "weight": 400 - } - } - } - }, - { - "endIndex": 881, - "startIndex": 877, - "textRun": { - "content": "code", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.21960784, - "green": 0.5019608, - "red": 0.09411765 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Roboto Mono", - "weight": 400 - } - } - } - }, - { - "endIndex": 882, - "startIndex": 881, - "textRun": { - "content": " ", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.44705883, - "green": 0.023529412, - "red": 0.72156864 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Roboto Mono", - "weight": 400 - } - } - } - }, - { - "endIndex": 887, - "startIndex": 882, - "textRun": { - "content": "block", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.21960784, - "green": 0.5019608, - "red": 0.09411765 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Roboto Mono", - "weight": 400 - } - } - } - }, - { - "endIndex": 888, - "startIndex": 887, - "textRun": { - "content": " ", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.44705883, - "green": 0.023529412, - "red": 0.72156864 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Roboto Mono", - "weight": 400 - } - } - } - }, - { - "endIndex": 895, - "startIndex": 888, - "textRun": { - "content": "example", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.21960784, - "green": 0.5019608, - "red": 0.09411765 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Roboto Mono", - "weight": 400 - } - } - } - }, - { - "endIndex": 896, - "startIndex": 895, - "textRun": { - "content": "\n", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.44705883, - "green": 0.023529412, - "red": 0.72156864 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Roboto Mono", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 863 - }, - { - "endIndex": 920, - "paragraph": { - "elements": [ - { - "endIndex": 904, - "startIndex": 896, - "textRun": { - "content": "function", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.21960784, - "green": 0.5019608, - "red": 0.09411765 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Roboto Mono", - "weight": 400 - } - } - } - }, - { - "endIndex": 905, - "startIndex": 904, - "textRun": { - "content": " ", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Roboto Mono", - "weight": 400 - } - } - } - }, - { - "endIndex": 917, - "startIndex": 905, - "textRun": { - "content": "helloWorld()", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.21960784, - "green": 0.5019608, - "red": 0.09411765 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Roboto Mono", - "weight": 400 - } - } - } - }, - { - "endIndex": 918, - "startIndex": 917, - "textRun": { - "content": " ", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Roboto Mono", - "weight": 400 - } - } - } - }, - { - "endIndex": 919, - "startIndex": 918, - "textRun": { - "content": "{", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.21960784, - "green": 0.5019608, - "red": 0.09411765 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Roboto Mono", - "weight": 400 - } - } - } - }, - { - "endIndex": 920, - "startIndex": 919, - "textRun": { - "content": "\n", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Roboto Mono", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 896 - }, - { - "endIndex": 952, - "paragraph": { - "elements": [ - { - "endIndex": 922, - "startIndex": 920, - "textRun": { - "content": " ", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Roboto Mono", - "weight": 400 - } - } - } - }, - { - "endIndex": 951, - "startIndex": 922, - "textRun": { - "content": "console.log(\"Hello, World!\");", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.21960784, - "green": 0.5019608, - "red": 0.09411765 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Roboto Mono", - "weight": 400 - } - } - } - }, - { - "endIndex": 952, - "startIndex": 951, - "textRun": { - "content": "\n", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "weightedFontFamily": { - "fontFamily": "Roboto Mono", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 920 - }, - { - "endIndex": 954, - "paragraph": { - "elements": [ - { - "endIndex": 953, - "startIndex": 952, - "textRun": { - "content": "}", - "textStyle": { - "fontSize": { - "magnitude": 9, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.21960784, - "green": 0.5019608, - "red": 0.09411765 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Roboto Mono", - "weight": 400 - } - } - } - }, - { - "endIndex": 954, - "startIndex": 953, - "textRun": { - "content": "\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Roboto Mono", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 952 - }, - { - "endIndex": 956, - "paragraph": { - "elements": [ - { - "endIndex": 956, - "startIndex": 954, - "textRun": { - "content": "\ue907\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": true, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 115, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 10, - "unit": "PT" - }, - "spacingMode": "NEVER_COLLAPSE" - } - }, - "startIndex": 954 - }, - { - "endIndex": 969, - "paragraph": { - "bullet": { - "listId": "kix.fwj8bl2edzyz", - "textStyle": { - "backgroundColor": {}, - "baselineOffset": "NONE", - "bold": false, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "italic": false, - "smallCaps": false, - "strikethrough": false, - "underline": true, - "weightedFontFamily": { - "fontFamily": "Cambria", - "weight": 400 - } - } - }, - "elements": [ - { - "endIndex": 968, - "startIndex": 956, - "textRun": { - "content": "Blockquotes:", - "textStyle": { - "bold": true - } - } - }, - { - "endIndex": 969, - "startIndex": 968, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 956 - }, - { - "endIndex": 970, - "paragraph": { - "elements": [ - { - "endIndex": 970, - "startIndex": 969, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": true, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 115, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 10, - "unit": "PT" - }, - "spacingMode": "NEVER_COLLAPSE" - } - }, - "startIndex": 969 - }, - { - "endIndex": 1093, - "paragraph": { - "elements": [ - { - "endIndex": 1092, - "startIndex": 970, - "textRun": { - "content": "This is an example of a blockquote, often used for citations or emphasized text that stands apart from the main narrative.", - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.21960784, - "green": 0.5019608, - "red": 0.09411765 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Roboto Mono", - "weight": 400 - } - } - } - }, - { - "endIndex": 1093, - "startIndex": 1092, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 970 - }, - { - "endIndex": 1094, - "paragraph": { - "elements": [ - { - "endIndex": 1094, - "startIndex": 1093, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 1093 - }, - { - "endIndex": 1096, - "paragraph": { - "elements": [ - { - "endIndex": 1095, - "horizontalRule": { - "textStyle": {} - }, - "startIndex": 1094 - }, - { - "endIndex": 1096, - "startIndex": 1095, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 1094 - }, - { - "endIndex": 1097, - "paragraph": { - "elements": [ - { - "endIndex": 1097, - "startIndex": 1096, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": true, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 115, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 10, - "unit": "PT" - }, - "spacingMode": "NEVER_COLLAPSE" - } - }, - "startIndex": 1096 - }, - { - "endIndex": 1142, - "paragraph": { - "elements": [ - { - "endIndex": 1142, - "startIndex": 1097, - "textRun": { - "content": "This is a horizontal rule (HR) placed above.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 1097 - }, - { - "endIndex": 1143, - "paragraph": { - "elements": [ - { - "endIndex": 1143, - "startIndex": 1142, - "textRun": { - "content": "\n", - "textStyle": { - "underline": true - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 1142 - }, - { - "endIndex": 1160, - "paragraph": { - "elements": [ - { - "endIndex": 1160, - "startIndex": 1143, - "textRun": { - "content": "Expected Outcome\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 1143 - }, - { - "endIndex": 1383, - "paragraph": { - "elements": [ - { - "endIndex": 1383, - "startIndex": 1160, - "textRun": { - "content": "All inline text styles (bold, italic, underline, and their combinations) should be represented in the structured content output with correct style attributes preserved. No formatting loss or incorrect nesting should occur.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 1160 - } - ] - }, - "documentStyle": { - "background": { - "color": {} - }, - "documentFormat": { - "documentMode": "PAGES" - }, - "marginBottom": { - "magnitude": 72, - "unit": "PT" - }, - "marginFooter": { - "magnitude": 36, - "unit": "PT" - }, - "marginHeader": { - "magnitude": 36, - "unit": "PT" - }, - "marginLeft": { - "magnitude": 90, - "unit": "PT" - }, - "marginRight": { - "magnitude": 90, - "unit": "PT" - }, - "marginTop": { - "magnitude": 72, - "unit": "PT" - }, - "pageNumberStart": 1, - "pageSize": { - "height": { - "magnitude": 792, - "unit": "PT" - }, - "width": { - "magnitude": 612, - "unit": "PT" - } - }, - "useCustomHeaderFooterMargins": true - }, - "lists": { - "kix.fwj8bl2edzyz": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphFormat": "%1", - "glyphSymbol": "\u25cb", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphFormat": "%2", - "glyphSymbol": "\u25a0", - "indentFirstLine": { - "magnitude": 90, - "unit": "PT" - }, - "indentStart": { - "magnitude": 108, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphFormat": "%3", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "magnitude": 126, - "unit": "PT" - }, - "indentStart": { - "magnitude": 144, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphFormat": "%4", - "glyphSymbol": "\u25cb", - "indentFirstLine": { - "magnitude": 162, - "unit": "PT" - }, - "indentStart": { - "magnitude": 180, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphFormat": "%5", - "glyphSymbol": "\u25a0", - "indentFirstLine": { - "magnitude": 198, - "unit": "PT" - }, - "indentStart": { - "magnitude": 216, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphFormat": "%6", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "magnitude": 234, - "unit": "PT" - }, - "indentStart": { - "magnitude": 252, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphFormat": "%7", - "glyphSymbol": "\u25cb", - "indentFirstLine": { - "magnitude": 270, - "unit": "PT" - }, - "indentStart": { - "magnitude": 288, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphFormat": "%8", - "glyphSymbol": "\u25a0", - "indentFirstLine": { - "magnitude": 306, - "unit": "PT" - }, - "indentStart": { - "magnitude": 324, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - } - ] - } - }, - "kix.list.1": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Noto Sans Symbols", - "weight": 400 - } - } - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.2": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Noto Sans Symbols", - "weight": 400 - } - } - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.3": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 54, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Noto Sans Symbols", - "weight": 400 - } - } - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.4": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Noto Sans Symbols", - "weight": 400 - } - } - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.5": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.6": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.7": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 54, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.8": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.9": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "magnitude": 72, - "unit": "PT" - }, - "indentStart": { - "magnitude": 90, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - } - }, - "namedStyles": { - "styles": [ - { - "namedStyleType": "NORMAL_TEXT", - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": true, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 115, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 10, - "unit": "PT" - }, - "spacingMode": "NEVER_COLLAPSE" - }, - "textStyle": { - "backgroundColor": {}, - "baselineOffset": "NONE", - "bold": false, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "italic": false, - "smallCaps": false, - "strikethrough": false, - "underline": false, - "weightedFontFamily": { - "fontFamily": "Cambria", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_1", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 24, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "bold": true, - "fontSize": { - "magnitude": 14, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.5686275, - "green": 0.3764706, - "red": 0.21176471 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_2", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "bold": true, - "fontSize": { - "magnitude": 13, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_3", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "bold": true, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_4", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "bold": true, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "italic": true, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_5", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.38039216, - "green": 0.24705882, - "red": 0.14117648 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_6", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.38039216, - "green": 0.24705882, - "red": 0.14117648 - } - } - }, - "italic": true, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "TITLE", - "paragraphStyle": { - "borderBottom": { - "color": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "dashStyle": "SOLID", - "padding": { - "magnitude": 4, - "unit": "PT" - }, - "width": { - "magnitude": 1, - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT", - "spaceBelow": { - "magnitude": 15, - "unit": "PT" - }, - "spacingMode": "NEVER_COLLAPSE" - }, - "textStyle": { - "fontSize": { - "magnitude": 26, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.3647059, - "green": 0.21176471, - "red": 0.09019608 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "SUBTITLE", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - }, - "textStyle": { - "fontSize": { - "magnitude": 12, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "italic": true, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - } - ] - } - }, - "tabProperties": { - "index": 0, - "tabId": "t.0", - "title": "Tab 1" - } - } - ], - "title": "Doc_2_Rich_Text_Formatting_Test" -} \ No newline at end of file diff --git a/apps/google-docs/src/utils/test_docs_json/Doc_3_Nested_Structures_And_References_Test.json b/apps/google-docs/src/utils/test_docs_json/Doc_3_Nested_Structures_And_References_Test.json deleted file mode 100644 index 5ce9e0758a..0000000000 --- a/apps/google-docs/src/utils/test_docs_json/Doc_3_Nested_Structures_And_References_Test.json +++ /dev/null @@ -1,2430 +0,0 @@ -{ - "documentId": "1NLGmOqvzFfZtHDBfhvUYaO5Z-v9O-WKe_KTDc5KOxRk", - "suggestionsViewMode": "PREVIEW_WITHOUT_SUGGESTIONS", - "tabs": [ - { - "documentTab": { - "body": { - "content": [ - { - "endIndex": 1, - "sectionBreak": { - "sectionStyle": { - "columnSeparatorStyle": "NONE", - "contentDirection": "LEFT_TO_RIGHT", - "sectionType": "CONTINUOUS" - } - } - }, - { - "endIndex": 55, - "paragraph": { - "elements": [ - { - "endIndex": 55, - "startIndex": 1, - "textRun": { - "content": "Document Import \u2014 Nested Structures & References Test\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_1" - } - }, - "startIndex": 1 - }, - { - "endIndex": 64, - "paragraph": { - "elements": [ - { - "endIndex": 64, - "startIndex": 55, - "textRun": { - "content": "Overview\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 55 - }, - { - "endIndex": 344, - "paragraph": { - "elements": [ - { - "endIndex": 344, - "startIndex": 64, - "textRun": { - "content": "This document is intended to test the importer\u2019s ability to handle nested structures, including multi-level lists, deeply nested headings, and references to other documents. It also includes cases that exceed normal nesting depth to ensure the importer gracefully handles limits.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 64 - }, - { - "endIndex": 368, - "paragraph": { - "elements": [ - { - "endIndex": 368, - "startIndex": 344, - "textRun": { - "content": "Nested Headings Example\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 344 - }, - { - "endIndex": 384, - "paragraph": { - "elements": [ - { - "endIndex": 384, - "startIndex": 368, - "textRun": { - "content": "Heading Level 1\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_1" - } - }, - "startIndex": 368 - }, - { - "endIndex": 400, - "paragraph": { - "elements": [ - { - "endIndex": 400, - "startIndex": 384, - "textRun": { - "content": "Heading Level 2\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 384 - }, - { - "endIndex": 416, - "paragraph": { - "elements": [ - { - "endIndex": 416, - "startIndex": 400, - "textRun": { - "content": "Heading Level 3\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_3" - } - }, - "startIndex": 400 - }, - { - "endIndex": 432, - "paragraph": { - "elements": [ - { - "endIndex": 432, - "startIndex": 416, - "textRun": { - "content": "Heading Level 4\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_4" - } - }, - "startIndex": 416 - }, - { - "endIndex": 448, - "paragraph": { - "elements": [ - { - "endIndex": 448, - "startIndex": 432, - "textRun": { - "content": "Heading Level 5\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_5" - } - }, - "startIndex": 432 - }, - { - "endIndex": 464, - "paragraph": { - "elements": [ - { - "endIndex": 464, - "startIndex": 448, - "textRun": { - "content": "Heading Level 6\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_6" - } - }, - "startIndex": 448 - }, - { - "endIndex": 538, - "paragraph": { - "elements": [ - { - "endIndex": 538, - "startIndex": 464, - "textRun": { - "content": "This section demonstrates heading depth beyond normal content structures.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 464 - }, - { - "endIndex": 563, - "paragraph": { - "elements": [ - { - "endIndex": 563, - "startIndex": 538, - "textRun": { - "content": "Multi-Level List Example\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 538 - }, - { - "endIndex": 580, - "paragraph": { - "bullet": { - "listId": "kix.list.1", - "textStyle": {} - }, - "elements": [ - { - "endIndex": 580, - "startIndex": 563, - "textRun": { - "content": "Level 1 \u2014 Item A\n", - "textStyle": { - "backgroundColor": {}, - "baselineOffset": "NONE", - "bold": false, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "italic": false, - "smallCaps": false, - "strikethrough": false, - "underline": false, - "weightedFontFamily": { - "fontFamily": "Cambria", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": true, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 115, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "spacingMode": "NEVER_COLLAPSE" - } - }, - "startIndex": 563 - }, - { - "endIndex": 613, - "paragraph": { - "bullet": { - "listId": "kix.list.1", - "textStyle": {} - }, - "elements": [ - { - "endIndex": 613, - "startIndex": 580, - "textRun": { - "content": " Level 2 \u2014 Item A.1 (indented)\n", - "textStyle": { - "backgroundColor": {}, - "baselineOffset": "NONE", - "bold": false, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "italic": false, - "smallCaps": false, - "strikethrough": false, - "underline": false, - "weightedFontFamily": { - "fontFamily": "Cambria", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": true, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 115, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "spacingMode": "NEVER_COLLAPSE" - } - }, - "startIndex": 580 - }, - { - "endIndex": 656, - "paragraph": { - "bullet": { - "listId": "kix.list.1", - "textStyle": {} - }, - "elements": [ - { - "endIndex": 656, - "startIndex": 613, - "textRun": { - "content": " Level 3 \u2014 Item A.1.1 (indented more)\n", - "textStyle": { - "backgroundColor": {}, - "baselineOffset": "NONE", - "bold": false, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "italic": false, - "smallCaps": false, - "strikethrough": false, - "underline": false, - "weightedFontFamily": { - "fontFamily": "Cambria", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": true, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 115, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "spacingMode": "NEVER_COLLAPSE" - } - }, - "startIndex": 613 - }, - { - "endIndex": 673, - "paragraph": { - "bullet": { - "listId": "kix.list.1", - "textStyle": {} - }, - "elements": [ - { - "endIndex": 673, - "startIndex": 656, - "textRun": { - "content": "Level 1 \u2014 Item B\n", - "textStyle": { - "backgroundColor": {}, - "baselineOffset": "NONE", - "bold": false, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "italic": false, - "smallCaps": false, - "strikethrough": false, - "underline": false, - "weightedFontFamily": { - "fontFamily": "Cambria", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": true, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 115, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 10, - "unit": "PT" - }, - "spacingMode": "NEVER_COLLAPSE" - } - }, - "startIndex": 656 - }, - { - "endIndex": 692, - "paragraph": { - "elements": [ - { - "endIndex": 692, - "startIndex": 673, - "textRun": { - "content": "References Example\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 673 - }, - { - "endIndex": 867, - "paragraph": { - "elements": [ - { - "endIndex": 867, - "startIndex": 692, - "textRun": { - "content": "This section includes references to other test documents used in the suite. These references should be detected and represented as relationships or cross-links in Contentful.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 692 - }, - { - "endIndex": 956, - "paragraph": { - "elements": [ - { - "endIndex": 888, - "startIndex": 867, - "textRun": { - "content": "Referenced Documents:", - "textStyle": { - "bold": true - } - } - }, - { - "endIndex": 899, - "startIndex": 888, - "textRun": { - "content": "\u000b- Doc 1 \u2014 ", - "textStyle": {} - } - }, - { - "endIndex": 919, - "startIndex": 899, - "textRun": { - "content": "Basic Structure Test", - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.8, - "green": 0.33333334, - "red": 0.06666667 - } - } - }, - "link": { - "url": "https://docs.google.com/document/d/12ohHh1SR47s-hm2lRtwK0NY-cRi_Nc6coAIrb9vylOM/edit?tab=t.0" - }, - "underline": true - } - } - }, - { - "endIndex": 930, - "startIndex": 919, - "textRun": { - "content": "\u000b- Doc 2 \u2014 ", - "textStyle": {} - } - }, - { - "endIndex": 955, - "startIndex": 930, - "textRun": { - "content": "Rich Text Formatting Test", - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.8, - "green": 0.33333334, - "red": 0.06666667 - } - } - }, - "link": { - "url": "https://docs.google.com/document/u/0/d/1Ufzr1TNu5SPEbrKHqPW_GA7iTH0A6jqB2FH5vaIHfcg/edit" - }, - "underline": true - } - } - }, - { - "endIndex": 956, - "startIndex": 955, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 867 - }, - { - "endIndex": 973, - "paragraph": { - "elements": [ - { - "endIndex": 973, - "startIndex": 956, - "textRun": { - "content": "Expected Outcome\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 956 - }, - { - "endIndex": 1216, - "paragraph": { - "elements": [ - { - "endIndex": 1216, - "startIndex": 973, - "textRun": { - "content": "Nested content should be preserved up to the supported depth. Any content exceeding nesting or reference depth limits should be handled gracefully. Circular references should be detected and safely ignored or reported without breaking import.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 973 - } - ] - }, - "documentStyle": { - "background": { - "color": {} - }, - "documentFormat": { - "documentMode": "PAGES" - }, - "marginBottom": { - "magnitude": 72, - "unit": "PT" - }, - "marginFooter": { - "magnitude": 36, - "unit": "PT" - }, - "marginHeader": { - "magnitude": 36, - "unit": "PT" - }, - "marginLeft": { - "magnitude": 90, - "unit": "PT" - }, - "marginRight": { - "magnitude": 90, - "unit": "PT" - }, - "marginTop": { - "magnitude": 72, - "unit": "PT" - }, - "pageNumberStart": 1, - "pageSize": { - "height": { - "magnitude": 792, - "unit": "PT" - }, - "width": { - "magnitude": 612, - "unit": "PT" - } - }, - "useCustomHeaderFooterMargins": true - }, - "lists": { - "kix.list.1": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Noto Sans Symbols", - "weight": 400 - } - } - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.2": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Noto Sans Symbols", - "weight": 400 - } - } - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.3": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 54, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Noto Sans Symbols", - "weight": 400 - } - } - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.4": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Noto Sans Symbols", - "weight": 400 - } - } - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.5": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.6": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.7": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 54, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.8": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.9": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "magnitude": 72, - "unit": "PT" - }, - "indentStart": { - "magnitude": 90, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - } - }, - "namedStyles": { - "styles": [ - { - "namedStyleType": "NORMAL_TEXT", - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": true, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 115, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 10, - "unit": "PT" - }, - "spacingMode": "NEVER_COLLAPSE" - }, - "textStyle": { - "backgroundColor": {}, - "baselineOffset": "NONE", - "bold": false, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "italic": false, - "smallCaps": false, - "strikethrough": false, - "underline": false, - "weightedFontFamily": { - "fontFamily": "Cambria", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_1", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 24, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "bold": true, - "fontSize": { - "magnitude": 14, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.5686275, - "green": 0.3764706, - "red": 0.21176471 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_2", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "bold": true, - "fontSize": { - "magnitude": 13, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_3", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "bold": true, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_4", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "bold": true, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "italic": true, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_5", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.38039216, - "green": 0.24705882, - "red": 0.14117648 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_6", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.38039216, - "green": 0.24705882, - "red": 0.14117648 - } - } - }, - "italic": true, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "TITLE", - "paragraphStyle": { - "borderBottom": { - "color": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "dashStyle": "SOLID", - "padding": { - "magnitude": 4, - "unit": "PT" - }, - "width": { - "magnitude": 1, - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT", - "spaceBelow": { - "magnitude": 15, - "unit": "PT" - }, - "spacingMode": "NEVER_COLLAPSE" - }, - "textStyle": { - "fontSize": { - "magnitude": 26, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.3647059, - "green": 0.21176471, - "red": 0.09019608 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "SUBTITLE", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - }, - "textStyle": { - "fontSize": { - "magnitude": 12, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "italic": true, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - } - ] - } - }, - "tabProperties": { - "index": 0, - "tabId": "t.0", - "title": "Tab 1" - } - } - ], - "title": "Doc_3_Nested_Structures_And_References_Test" -} \ No newline at end of file diff --git a/apps/google-docs/src/utils/test_docs_json/Doc_4_Media_Embeds_Test.json b/apps/google-docs/src/utils/test_docs_json/Doc_4_Media_Embeds_Test.json deleted file mode 100644 index 178a526341..0000000000 --- a/apps/google-docs/src/utils/test_docs_json/Doc_4_Media_Embeds_Test.json +++ /dev/null @@ -1,2040 +0,0 @@ -{ - "documentId": "1p_LoSc6rDQsfiuxneHwtpIyXAweLz0FxOzVVQe2kAVs", - "suggestionsViewMode": "PREVIEW_WITHOUT_SUGGESTIONS", - "tabs": [ - { - "documentTab": { - "body": { - "content": [ - { - "endIndex": 1, - "sectionBreak": { - "sectionStyle": { - "columnSeparatorStyle": "NONE", - "contentDirection": "LEFT_TO_RIGHT", - "sectionType": "CONTINUOUS" - } - } - }, - { - "endIndex": 37, - "paragraph": { - "elements": [ - { - "endIndex": 37, - "startIndex": 1, - "textRun": { - "content": "Document Import \u2014 Media Embeds Test\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_1" - } - }, - "startIndex": 1 - }, - { - "endIndex": 46, - "paragraph": { - "elements": [ - { - "endIndex": 46, - "startIndex": 37, - "textRun": { - "content": "Overview\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 37 - }, - { - "endIndex": 349, - "paragraph": { - "elements": [ - { - "endIndex": 349, - "startIndex": 46, - "textRun": { - "content": "This document is intended to validate that the importer correctly identifies and processes embedded media elements such as images, captions, and external media embeds (e.g., YouTube videos or Google Drawings). The goal is to ensure that media objects are imported with correct metadata and positioning.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 46 - }, - { - "endIndex": 364, - "paragraph": { - "elements": [ - { - "endIndex": 364, - "startIndex": 349, - "textRun": { - "content": "Image Examples\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 349 - }, - { - "endIndex": 563, - "paragraph": { - "elements": [ - { - "endIndex": 563, - "startIndex": 364, - "textRun": { - "content": "Below are placeholder references for images that should be tested during the import process. Replace these descriptions with actual image inserts in the Google Docs version to validate asset import.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 364 - }, - { - "endIndex": 565, - "paragraph": { - "elements": [ - { - "endIndex": 564, - "inlineObjectElement": { - "inlineObjectId": "kix.r4b17zjhyycx", - "textStyle": {} - }, - "startIndex": 563 - }, - { - "endIndex": 565, - "startIndex": 564, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 563 - }, - { - "endIndex": 567, - "paragraph": { - "elements": [ - { - "endIndex": 566, - "inlineObjectElement": { - "inlineObjectId": "kix.ix5oc1rpyqg1", - "textStyle": {} - }, - "startIndex": 565 - }, - { - "endIndex": 567, - "startIndex": 566, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 565 - }, - { - "endIndex": 591, - "paragraph": { - "elements": [ - { - "endIndex": 591, - "startIndex": 567, - "textRun": { - "content": "Embedded Media Examples\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 567 - }, - { - "endIndex": 740, - "paragraph": { - "elements": [ - { - "endIndex": 740, - "startIndex": 591, - "textRun": { - "content": "This section contains text placeholders for external embeds. When converting to Google Docs, actual embedded content should be inserted for testing.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 591 - }, - { - "endIndex": 742, - "paragraph": { - "elements": [ - { - "endIndex": 741, - "richLink": { - "richLinkId": "kix.c2zu47dosj3v", - "richLinkProperties": { - "title": "Contentful Platform: Overview + Demo", - "uri": "https://www.youtube.com/watch?v=uLd5pJi3OYM" - }, - "textStyle": {} - }, - "startIndex": 740 - }, - { - "endIndex": 742, - "startIndex": 741, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 740 - }, - { - "endIndex": 744, - "paragraph": { - "elements": [ - { - "endIndex": 743, - "richLink": { - "richLinkId": "kix.6m5hlc69y106", - "richLinkProperties": { - "title": "Headless CMS | Part 1: Introduction to Contentful", - "uri": "https://www.youtube.com/watch?v=dor1VKFwAl4" - }, - "textStyle": {} - }, - "startIndex": 742 - }, - { - "endIndex": 744, - "startIndex": 743, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 742 - }, - { - "endIndex": 766, - "paragraph": { - "elements": [ - { - "endIndex": 766, - "startIndex": 744, - "textRun": { - "content": "Media Context Example\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 744 - }, - { - "endIndex": 919, - "paragraph": { - "elements": [ - { - "endIndex": 829, - "startIndex": 766, - "textRun": { - "content": "This is an example paragraph with an image placeholder inline: ", - "textStyle": {} - } - }, - { - "endIndex": 830, - "inlineObjectElement": { - "inlineObjectId": "kix.qzqzrlhpvms8", - "textStyle": {} - }, - "startIndex": 829 - }, - { - "endIndex": 919, - "startIndex": 830, - "textRun": { - "content": ". The importer should maintain the media placement relative to surrounding text content.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 766 - }, - { - "endIndex": 936, - "paragraph": { - "elements": [ - { - "endIndex": 936, - "startIndex": 919, - "textRun": { - "content": "Expected Outcome\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 919 - }, - { - "endIndex": 1214, - "paragraph": { - "elements": [ - { - "endIndex": 1214, - "startIndex": 936, - "textRun": { - "content": "Media assets should import with correct order, captions, and contextual placement. Inline images should remain embedded in related text, and external embeds (e.g., videos, drawings) should appear as linked content. Missing media should be flagged without causing import errors.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 936 - } - ] - }, - "documentStyle": { - "background": { - "color": {} - }, - "documentFormat": { - "documentMode": "PAGES" - }, - "marginBottom": { - "magnitude": 72, - "unit": "PT" - }, - "marginFooter": { - "magnitude": 36, - "unit": "PT" - }, - "marginHeader": { - "magnitude": 36, - "unit": "PT" - }, - "marginLeft": { - "magnitude": 90, - "unit": "PT" - }, - "marginRight": { - "magnitude": 90, - "unit": "PT" - }, - "marginTop": { - "magnitude": 72, - "unit": "PT" - }, - "pageNumberStart": 1, - "pageSize": { - "height": { - "magnitude": 792, - "unit": "PT" - }, - "width": { - "magnitude": 612, - "unit": "PT" - } - }, - "useCustomHeaderFooterMargins": true - }, - "inlineObjects": { - "kix.ix5oc1rpyqg1": { - "inlineObjectProperties": { - "embeddedObject": { - "embeddedObjectBorder": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "propertyState": "NOT_RENDERED", - "width": { - "unit": "PT" - } - }, - "imageProperties": { - "contentUri": "https://lh7-rt.googleusercontent.com/docsz/AD_4nXc1uaXTu7tNpRnV3c4TwNXwNwixRnsZoLorp2yCL1BL9n5Vzv2H107Ea79fvRRyJlfmzSVb-Yx2CrzyXXiTo7qA2mznNDV52aLjJDAsOlptzlNbU-qLeeHUwItLKFYT_sgNtvBQcsYimQSNbL6q_CCNM2fV?key=5mLCyMA29Yboa2INQ3olIA", - "cropProperties": {} - }, - "marginBottom": { - "magnitude": 9, - "unit": "PT" - }, - "marginLeft": { - "magnitude": 9, - "unit": "PT" - }, - "marginRight": { - "magnitude": 9, - "unit": "PT" - }, - "marginTop": { - "magnitude": 9, - "unit": "PT" - }, - "size": { - "height": { - "magnitude": 89, - "unit": "PT" - }, - "width": { - "magnitude": 432, - "unit": "PT" - } - } - } - }, - "objectId": "kix.ix5oc1rpyqg1" - }, - "kix.qzqzrlhpvms8": { - "inlineObjectProperties": { - "embeddedObject": { - "embeddedObjectBorder": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "propertyState": "NOT_RENDERED", - "width": { - "unit": "PT" - } - }, - "imageProperties": { - "contentUri": "https://lh7-rt.googleusercontent.com/docsz/AD_4nXfeOPnRy7ULMVDY35HvmpsxhS3UGa3EAKNiYHIWMLlkxb0pBvsMHjIfysGB8vFBKnP3iq2ofcGu03t-w_MEKQl1ZDBj-VUWcOFHRZwZdglSSCSKM8zEg6LLLPfOs_nhk8zHm4UNBMF2bFF3EORGoGsffLWo?key=5mLCyMA29Yboa2INQ3olIA", - "cropProperties": {} - }, - "marginBottom": { - "magnitude": 9, - "unit": "PT" - }, - "marginLeft": { - "magnitude": 9, - "unit": "PT" - }, - "marginRight": { - "magnitude": 9, - "unit": "PT" - }, - "marginTop": { - "magnitude": 9, - "unit": "PT" - }, - "size": { - "height": { - "magnitude": 56.54509277343743, - "unit": "PT" - }, - "width": { - "magnitude": 101.15865220936054, - "unit": "PT" - } - } - } - }, - "objectId": "kix.qzqzrlhpvms8" - }, - "kix.r4b17zjhyycx": { - "inlineObjectProperties": { - "embeddedObject": { - "embeddedObjectBorder": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "propertyState": "NOT_RENDERED", - "width": { - "unit": "PT" - } - }, - "imageProperties": { - "contentUri": "https://lh7-rt.googleusercontent.com/docsz/AD_4nXdp5ab3Adf1MZkFdy6WuZQlUqE4Rqyhnp7FB2_kJy951JzNlTAYFUiRsjOYpn0wnfwgtXrCuOtz_i9aIGQAzD1-7OpMr9JvUDDwuzMUCoX_Vk6cVZd67QfiU1mxUMu06xFRy2-pJY2saQ7NmlwOhPSUWsiZ?key=5mLCyMA29Yboa2INQ3olIA", - "cropProperties": {} - }, - "marginBottom": { - "magnitude": 9, - "unit": "PT" - }, - "marginLeft": { - "magnitude": 9, - "unit": "PT" - }, - "marginRight": { - "magnitude": 9, - "unit": "PT" - }, - "marginTop": { - "magnitude": 9, - "unit": "PT" - }, - "size": { - "height": { - "magnitude": 150, - "unit": "PT" - }, - "width": { - "magnitude": 150, - "unit": "PT" - } - } - } - }, - "objectId": "kix.r4b17zjhyycx" - } - }, - "lists": { - "kix.list.1": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Noto Sans Symbols", - "weight": 400 - } - } - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.2": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Noto Sans Symbols", - "weight": 400 - } - } - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.3": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 54, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Noto Sans Symbols", - "weight": 400 - } - } - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.4": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Noto Sans Symbols", - "weight": 400 - } - } - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.5": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.6": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.7": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 54, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.8": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.9": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "magnitude": 72, - "unit": "PT" - }, - "indentStart": { - "magnitude": 90, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - } - }, - "namedStyles": { - "styles": [ - { - "namedStyleType": "NORMAL_TEXT", - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": true, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 115, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 10, - "unit": "PT" - }, - "spacingMode": "NEVER_COLLAPSE" - }, - "textStyle": { - "backgroundColor": {}, - "baselineOffset": "NONE", - "bold": false, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "italic": false, - "smallCaps": false, - "strikethrough": false, - "underline": false, - "weightedFontFamily": { - "fontFamily": "Cambria", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_1", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 24, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "bold": true, - "fontSize": { - "magnitude": 14, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.5686275, - "green": 0.3764706, - "red": 0.21176471 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_2", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "bold": true, - "fontSize": { - "magnitude": 13, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_3", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "bold": true, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_4", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "bold": true, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "italic": true, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_5", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.38039216, - "green": 0.24705882, - "red": 0.14117648 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_6", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.38039216, - "green": 0.24705882, - "red": 0.14117648 - } - } - }, - "italic": true, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "TITLE", - "paragraphStyle": { - "borderBottom": { - "color": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "dashStyle": "SOLID", - "padding": { - "magnitude": 4, - "unit": "PT" - }, - "width": { - "magnitude": 1, - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT", - "spaceBelow": { - "magnitude": 15, - "unit": "PT" - }, - "spacingMode": "NEVER_COLLAPSE" - }, - "textStyle": { - "fontSize": { - "magnitude": 26, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.3647059, - "green": 0.21176471, - "red": 0.09019608 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "SUBTITLE", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - }, - "textStyle": { - "fontSize": { - "magnitude": 12, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "italic": true, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - } - ] - } - }, - "tabProperties": { - "index": 0, - "tabId": "t.0", - "title": "Tab 1" - } - } - ], - "title": "Doc_4_Media_Embeds_Test" -} \ No newline at end of file diff --git a/apps/google-docs/src/utils/test_docs_json/Doc_5_Bulk_Entry_Stress_Test.json b/apps/google-docs/src/utils/test_docs_json/Doc_5_Bulk_Entry_Stress_Test.json deleted file mode 100644 index 89106e2445..0000000000 --- a/apps/google-docs/src/utils/test_docs_json/Doc_5_Bulk_Entry_Stress_Test.json +++ /dev/null @@ -1,6435 +0,0 @@ -{ - "documentId": "1TVfsjJIphBnMi0qNFCJzZ1boCS0H0YZymA2H0gC6UKs", - "suggestionsViewMode": "PREVIEW_WITHOUT_SUGGESTIONS", - "tabs": [ - { - "documentTab": { - "body": { - "content": [ - { - "endIndex": 1, - "sectionBreak": { - "sectionStyle": { - "columnSeparatorStyle": "NONE", - "contentDirection": "LEFT_TO_RIGHT", - "sectionType": "CONTINUOUS" - } - } - }, - { - "endIndex": 42, - "paragraph": { - "elements": [ - { - "endIndex": 42, - "startIndex": 1, - "textRun": { - "content": "Document Import \u2014 Bulk Entry Stress Test\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_1" - } - }, - "startIndex": 1 - }, - { - "endIndex": 51, - "paragraph": { - "elements": [ - { - "endIndex": 51, - "startIndex": 42, - "textRun": { - "content": "Overview\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 42 - }, - { - "endIndex": 302, - "paragraph": { - "elements": [ - { - "endIndex": 302, - "startIndex": 51, - "textRun": { - "content": "This document is designed to test the importer's ability to handle large-scale document parsing, specifically to validate rate-limit management and pagination logic. It contains over 100 heading-and-paragraph pairs to simulate heavy import workloads.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 51 - }, - { - "endIndex": 310, - "paragraph": { - "elements": [ - { - "endIndex": 310, - "startIndex": 302, - "textRun": { - "content": "Entry 1\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 302 - }, - { - "endIndex": 497, - "paragraph": { - "elements": [ - { - "endIndex": 497, - "startIndex": 310, - "textRun": { - "content": "This is the content paragraph for Entry 1. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 1.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 310 - }, - { - "endIndex": 505, - "paragraph": { - "elements": [ - { - "endIndex": 505, - "startIndex": 497, - "textRun": { - "content": "Entry 2\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 497 - }, - { - "endIndex": 692, - "paragraph": { - "elements": [ - { - "endIndex": 692, - "startIndex": 505, - "textRun": { - "content": "This is the content paragraph for Entry 2. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 2.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 505 - }, - { - "endIndex": 700, - "paragraph": { - "elements": [ - { - "endIndex": 700, - "startIndex": 692, - "textRun": { - "content": "Entry 3\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 692 - }, - { - "endIndex": 887, - "paragraph": { - "elements": [ - { - "endIndex": 887, - "startIndex": 700, - "textRun": { - "content": "This is the content paragraph for Entry 3. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 3.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 700 - }, - { - "endIndex": 895, - "paragraph": { - "elements": [ - { - "endIndex": 895, - "startIndex": 887, - "textRun": { - "content": "Entry 4\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 887 - }, - { - "endIndex": 1082, - "paragraph": { - "elements": [ - { - "endIndex": 1082, - "startIndex": 895, - "textRun": { - "content": "This is the content paragraph for Entry 4. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 4.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 895 - }, - { - "endIndex": 1090, - "paragraph": { - "elements": [ - { - "endIndex": 1090, - "startIndex": 1082, - "textRun": { - "content": "Entry 5\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 1082 - }, - { - "endIndex": 1277, - "paragraph": { - "elements": [ - { - "endIndex": 1277, - "startIndex": 1090, - "textRun": { - "content": "This is the content paragraph for Entry 5. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 5.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 1090 - }, - { - "endIndex": 1285, - "paragraph": { - "elements": [ - { - "endIndex": 1285, - "startIndex": 1277, - "textRun": { - "content": "Entry 6\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 1277 - }, - { - "endIndex": 1472, - "paragraph": { - "elements": [ - { - "endIndex": 1472, - "startIndex": 1285, - "textRun": { - "content": "This is the content paragraph for Entry 6. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 6.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 1285 - }, - { - "endIndex": 1480, - "paragraph": { - "elements": [ - { - "endIndex": 1480, - "startIndex": 1472, - "textRun": { - "content": "Entry 7\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 1472 - }, - { - "endIndex": 1667, - "paragraph": { - "elements": [ - { - "endIndex": 1667, - "startIndex": 1480, - "textRun": { - "content": "This is the content paragraph for Entry 7. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 7.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 1480 - }, - { - "endIndex": 1675, - "paragraph": { - "elements": [ - { - "endIndex": 1675, - "startIndex": 1667, - "textRun": { - "content": "Entry 8\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 1667 - }, - { - "endIndex": 1862, - "paragraph": { - "elements": [ - { - "endIndex": 1862, - "startIndex": 1675, - "textRun": { - "content": "This is the content paragraph for Entry 8. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 8.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 1675 - }, - { - "endIndex": 1870, - "paragraph": { - "elements": [ - { - "endIndex": 1870, - "startIndex": 1862, - "textRun": { - "content": "Entry 9\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 1862 - }, - { - "endIndex": 2057, - "paragraph": { - "elements": [ - { - "endIndex": 2057, - "startIndex": 1870, - "textRun": { - "content": "This is the content paragraph for Entry 9. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 9.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 1870 - }, - { - "endIndex": 2066, - "paragraph": { - "elements": [ - { - "endIndex": 2066, - "startIndex": 2057, - "textRun": { - "content": "Entry 10\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 2057 - }, - { - "endIndex": 2255, - "paragraph": { - "elements": [ - { - "endIndex": 2255, - "startIndex": 2066, - "textRun": { - "content": "This is the content paragraph for Entry 10. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 10.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 2066 - }, - { - "endIndex": 2264, - "paragraph": { - "elements": [ - { - "endIndex": 2264, - "startIndex": 2255, - "textRun": { - "content": "Entry 11\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 2255 - }, - { - "endIndex": 2453, - "paragraph": { - "elements": [ - { - "endIndex": 2453, - "startIndex": 2264, - "textRun": { - "content": "This is the content paragraph for Entry 11. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 11.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 2264 - }, - { - "endIndex": 2462, - "paragraph": { - "elements": [ - { - "endIndex": 2462, - "startIndex": 2453, - "textRun": { - "content": "Entry 12\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 2453 - }, - { - "endIndex": 2651, - "paragraph": { - "elements": [ - { - "endIndex": 2651, - "startIndex": 2462, - "textRun": { - "content": "This is the content paragraph for Entry 12. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 12.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 2462 - }, - { - "endIndex": 2660, - "paragraph": { - "elements": [ - { - "endIndex": 2660, - "startIndex": 2651, - "textRun": { - "content": "Entry 13\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 2651 - }, - { - "endIndex": 2849, - "paragraph": { - "elements": [ - { - "endIndex": 2849, - "startIndex": 2660, - "textRun": { - "content": "This is the content paragraph for Entry 13. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 13.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 2660 - }, - { - "endIndex": 2858, - "paragraph": { - "elements": [ - { - "endIndex": 2858, - "startIndex": 2849, - "textRun": { - "content": "Entry 14\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 2849 - }, - { - "endIndex": 3047, - "paragraph": { - "elements": [ - { - "endIndex": 3047, - "startIndex": 2858, - "textRun": { - "content": "This is the content paragraph for Entry 14. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 14.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 2858 - }, - { - "endIndex": 3056, - "paragraph": { - "elements": [ - { - "endIndex": 3056, - "startIndex": 3047, - "textRun": { - "content": "Entry 15\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 3047 - }, - { - "endIndex": 3245, - "paragraph": { - "elements": [ - { - "endIndex": 3245, - "startIndex": 3056, - "textRun": { - "content": "This is the content paragraph for Entry 15. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 15.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 3056 - }, - { - "endIndex": 3254, - "paragraph": { - "elements": [ - { - "endIndex": 3254, - "startIndex": 3245, - "textRun": { - "content": "Entry 16\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 3245 - }, - { - "endIndex": 3443, - "paragraph": { - "elements": [ - { - "endIndex": 3443, - "startIndex": 3254, - "textRun": { - "content": "This is the content paragraph for Entry 16. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 16.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 3254 - }, - { - "endIndex": 3452, - "paragraph": { - "elements": [ - { - "endIndex": 3452, - "startIndex": 3443, - "textRun": { - "content": "Entry 17\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 3443 - }, - { - "endIndex": 3641, - "paragraph": { - "elements": [ - { - "endIndex": 3641, - "startIndex": 3452, - "textRun": { - "content": "This is the content paragraph for Entry 17. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 17.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 3452 - }, - { - "endIndex": 3650, - "paragraph": { - "elements": [ - { - "endIndex": 3650, - "startIndex": 3641, - "textRun": { - "content": "Entry 18\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 3641 - }, - { - "endIndex": 3839, - "paragraph": { - "elements": [ - { - "endIndex": 3839, - "startIndex": 3650, - "textRun": { - "content": "This is the content paragraph for Entry 18. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 18.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 3650 - }, - { - "endIndex": 3848, - "paragraph": { - "elements": [ - { - "endIndex": 3848, - "startIndex": 3839, - "textRun": { - "content": "Entry 19\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 3839 - }, - { - "endIndex": 4037, - "paragraph": { - "elements": [ - { - "endIndex": 4037, - "startIndex": 3848, - "textRun": { - "content": "This is the content paragraph for Entry 19. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 19.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 3848 - }, - { - "endIndex": 4046, - "paragraph": { - "elements": [ - { - "endIndex": 4046, - "startIndex": 4037, - "textRun": { - "content": "Entry 20\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 4037 - }, - { - "endIndex": 4235, - "paragraph": { - "elements": [ - { - "endIndex": 4235, - "startIndex": 4046, - "textRun": { - "content": "This is the content paragraph for Entry 20. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 20.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 4046 - }, - { - "endIndex": 4244, - "paragraph": { - "elements": [ - { - "endIndex": 4244, - "startIndex": 4235, - "textRun": { - "content": "Entry 21\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 4235 - }, - { - "endIndex": 4433, - "paragraph": { - "elements": [ - { - "endIndex": 4433, - "startIndex": 4244, - "textRun": { - "content": "This is the content paragraph for Entry 21. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 21.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 4244 - }, - { - "endIndex": 4442, - "paragraph": { - "elements": [ - { - "endIndex": 4442, - "startIndex": 4433, - "textRun": { - "content": "Entry 22\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 4433 - }, - { - "endIndex": 4631, - "paragraph": { - "elements": [ - { - "endIndex": 4631, - "startIndex": 4442, - "textRun": { - "content": "This is the content paragraph for Entry 22. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 22.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 4442 - }, - { - "endIndex": 4640, - "paragraph": { - "elements": [ - { - "endIndex": 4640, - "startIndex": 4631, - "textRun": { - "content": "Entry 23\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 4631 - }, - { - "endIndex": 4829, - "paragraph": { - "elements": [ - { - "endIndex": 4829, - "startIndex": 4640, - "textRun": { - "content": "This is the content paragraph for Entry 23. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 23.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 4640 - }, - { - "endIndex": 4838, - "paragraph": { - "elements": [ - { - "endIndex": 4838, - "startIndex": 4829, - "textRun": { - "content": "Entry 24\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 4829 - }, - { - "endIndex": 5027, - "paragraph": { - "elements": [ - { - "endIndex": 5027, - "startIndex": 4838, - "textRun": { - "content": "This is the content paragraph for Entry 24. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 24.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 4838 - }, - { - "endIndex": 5036, - "paragraph": { - "elements": [ - { - "endIndex": 5036, - "startIndex": 5027, - "textRun": { - "content": "Entry 25\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 5027 - }, - { - "endIndex": 5225, - "paragraph": { - "elements": [ - { - "endIndex": 5225, - "startIndex": 5036, - "textRun": { - "content": "This is the content paragraph for Entry 25. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 25.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 5036 - }, - { - "endIndex": 5234, - "paragraph": { - "elements": [ - { - "endIndex": 5234, - "startIndex": 5225, - "textRun": { - "content": "Entry 26\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 5225 - }, - { - "endIndex": 5423, - "paragraph": { - "elements": [ - { - "endIndex": 5423, - "startIndex": 5234, - "textRun": { - "content": "This is the content paragraph for Entry 26. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 26.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 5234 - }, - { - "endIndex": 5432, - "paragraph": { - "elements": [ - { - "endIndex": 5432, - "startIndex": 5423, - "textRun": { - "content": "Entry 27\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 5423 - }, - { - "endIndex": 5621, - "paragraph": { - "elements": [ - { - "endIndex": 5621, - "startIndex": 5432, - "textRun": { - "content": "This is the content paragraph for Entry 27. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 27.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 5432 - }, - { - "endIndex": 5630, - "paragraph": { - "elements": [ - { - "endIndex": 5630, - "startIndex": 5621, - "textRun": { - "content": "Entry 28\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 5621 - }, - { - "endIndex": 5819, - "paragraph": { - "elements": [ - { - "endIndex": 5819, - "startIndex": 5630, - "textRun": { - "content": "This is the content paragraph for Entry 28. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 28.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 5630 - }, - { - "endIndex": 5828, - "paragraph": { - "elements": [ - { - "endIndex": 5828, - "startIndex": 5819, - "textRun": { - "content": "Entry 29\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 5819 - }, - { - "endIndex": 6017, - "paragraph": { - "elements": [ - { - "endIndex": 6017, - "startIndex": 5828, - "textRun": { - "content": "This is the content paragraph for Entry 29. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 29.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 5828 - }, - { - "endIndex": 6026, - "paragraph": { - "elements": [ - { - "endIndex": 6026, - "startIndex": 6017, - "textRun": { - "content": "Entry 30\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 6017 - }, - { - "endIndex": 6215, - "paragraph": { - "elements": [ - { - "endIndex": 6215, - "startIndex": 6026, - "textRun": { - "content": "This is the content paragraph for Entry 30. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 30.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 6026 - }, - { - "endIndex": 6224, - "paragraph": { - "elements": [ - { - "endIndex": 6224, - "startIndex": 6215, - "textRun": { - "content": "Entry 31\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 6215 - }, - { - "endIndex": 6413, - "paragraph": { - "elements": [ - { - "endIndex": 6413, - "startIndex": 6224, - "textRun": { - "content": "This is the content paragraph for Entry 31. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 31.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 6224 - }, - { - "endIndex": 6422, - "paragraph": { - "elements": [ - { - "endIndex": 6422, - "startIndex": 6413, - "textRun": { - "content": "Entry 32\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 6413 - }, - { - "endIndex": 6611, - "paragraph": { - "elements": [ - { - "endIndex": 6611, - "startIndex": 6422, - "textRun": { - "content": "This is the content paragraph for Entry 32. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 32.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 6422 - }, - { - "endIndex": 6620, - "paragraph": { - "elements": [ - { - "endIndex": 6620, - "startIndex": 6611, - "textRun": { - "content": "Entry 33\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 6611 - }, - { - "endIndex": 6809, - "paragraph": { - "elements": [ - { - "endIndex": 6809, - "startIndex": 6620, - "textRun": { - "content": "This is the content paragraph for Entry 33. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 33.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 6620 - }, - { - "endIndex": 6818, - "paragraph": { - "elements": [ - { - "endIndex": 6818, - "startIndex": 6809, - "textRun": { - "content": "Entry 34\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 6809 - }, - { - "endIndex": 7007, - "paragraph": { - "elements": [ - { - "endIndex": 7007, - "startIndex": 6818, - "textRun": { - "content": "This is the content paragraph for Entry 34. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 34.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 6818 - }, - { - "endIndex": 7016, - "paragraph": { - "elements": [ - { - "endIndex": 7016, - "startIndex": 7007, - "textRun": { - "content": "Entry 35\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 7007 - }, - { - "endIndex": 7205, - "paragraph": { - "elements": [ - { - "endIndex": 7205, - "startIndex": 7016, - "textRun": { - "content": "This is the content paragraph for Entry 35. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 35.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 7016 - }, - { - "endIndex": 7214, - "paragraph": { - "elements": [ - { - "endIndex": 7214, - "startIndex": 7205, - "textRun": { - "content": "Entry 36\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 7205 - }, - { - "endIndex": 7403, - "paragraph": { - "elements": [ - { - "endIndex": 7403, - "startIndex": 7214, - "textRun": { - "content": "This is the content paragraph for Entry 36. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 36.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 7214 - }, - { - "endIndex": 7412, - "paragraph": { - "elements": [ - { - "endIndex": 7412, - "startIndex": 7403, - "textRun": { - "content": "Entry 37\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 7403 - }, - { - "endIndex": 7601, - "paragraph": { - "elements": [ - { - "endIndex": 7601, - "startIndex": 7412, - "textRun": { - "content": "This is the content paragraph for Entry 37. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 37.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 7412 - }, - { - "endIndex": 7610, - "paragraph": { - "elements": [ - { - "endIndex": 7610, - "startIndex": 7601, - "textRun": { - "content": "Entry 38\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 7601 - }, - { - "endIndex": 7799, - "paragraph": { - "elements": [ - { - "endIndex": 7799, - "startIndex": 7610, - "textRun": { - "content": "This is the content paragraph for Entry 38. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 38.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 7610 - }, - { - "endIndex": 7808, - "paragraph": { - "elements": [ - { - "endIndex": 7808, - "startIndex": 7799, - "textRun": { - "content": "Entry 39\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 7799 - }, - { - "endIndex": 7997, - "paragraph": { - "elements": [ - { - "endIndex": 7997, - "startIndex": 7808, - "textRun": { - "content": "This is the content paragraph for Entry 39. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 39.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 7808 - }, - { - "endIndex": 8006, - "paragraph": { - "elements": [ - { - "endIndex": 8006, - "startIndex": 7997, - "textRun": { - "content": "Entry 40\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 7997 - }, - { - "endIndex": 8195, - "paragraph": { - "elements": [ - { - "endIndex": 8195, - "startIndex": 8006, - "textRun": { - "content": "This is the content paragraph for Entry 40. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 40.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 8006 - }, - { - "endIndex": 8204, - "paragraph": { - "elements": [ - { - "endIndex": 8204, - "startIndex": 8195, - "textRun": { - "content": "Entry 41\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 8195 - }, - { - "endIndex": 8393, - "paragraph": { - "elements": [ - { - "endIndex": 8393, - "startIndex": 8204, - "textRun": { - "content": "This is the content paragraph for Entry 41. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 41.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 8204 - }, - { - "endIndex": 8402, - "paragraph": { - "elements": [ - { - "endIndex": 8402, - "startIndex": 8393, - "textRun": { - "content": "Entry 42\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 8393 - }, - { - "endIndex": 8591, - "paragraph": { - "elements": [ - { - "endIndex": 8591, - "startIndex": 8402, - "textRun": { - "content": "This is the content paragraph for Entry 42. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 42.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 8402 - }, - { - "endIndex": 8600, - "paragraph": { - "elements": [ - { - "endIndex": 8600, - "startIndex": 8591, - "textRun": { - "content": "Entry 43\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 8591 - }, - { - "endIndex": 8789, - "paragraph": { - "elements": [ - { - "endIndex": 8789, - "startIndex": 8600, - "textRun": { - "content": "This is the content paragraph for Entry 43. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 43.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 8600 - }, - { - "endIndex": 8798, - "paragraph": { - "elements": [ - { - "endIndex": 8798, - "startIndex": 8789, - "textRun": { - "content": "Entry 44\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 8789 - }, - { - "endIndex": 8987, - "paragraph": { - "elements": [ - { - "endIndex": 8987, - "startIndex": 8798, - "textRun": { - "content": "This is the content paragraph for Entry 44. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 44.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 8798 - }, - { - "endIndex": 8996, - "paragraph": { - "elements": [ - { - "endIndex": 8996, - "startIndex": 8987, - "textRun": { - "content": "Entry 45\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 8987 - }, - { - "endIndex": 9185, - "paragraph": { - "elements": [ - { - "endIndex": 9185, - "startIndex": 8996, - "textRun": { - "content": "This is the content paragraph for Entry 45. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 45.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 8996 - }, - { - "endIndex": 9194, - "paragraph": { - "elements": [ - { - "endIndex": 9194, - "startIndex": 9185, - "textRun": { - "content": "Entry 46\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 9185 - }, - { - "endIndex": 9383, - "paragraph": { - "elements": [ - { - "endIndex": 9383, - "startIndex": 9194, - "textRun": { - "content": "This is the content paragraph for Entry 46. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 46.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 9194 - }, - { - "endIndex": 9392, - "paragraph": { - "elements": [ - { - "endIndex": 9392, - "startIndex": 9383, - "textRun": { - "content": "Entry 47\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 9383 - }, - { - "endIndex": 9581, - "paragraph": { - "elements": [ - { - "endIndex": 9581, - "startIndex": 9392, - "textRun": { - "content": "This is the content paragraph for Entry 47. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 47.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 9392 - }, - { - "endIndex": 9590, - "paragraph": { - "elements": [ - { - "endIndex": 9590, - "startIndex": 9581, - "textRun": { - "content": "Entry 48\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 9581 - }, - { - "endIndex": 9779, - "paragraph": { - "elements": [ - { - "endIndex": 9779, - "startIndex": 9590, - "textRun": { - "content": "This is the content paragraph for Entry 48. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 48.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 9590 - }, - { - "endIndex": 9788, - "paragraph": { - "elements": [ - { - "endIndex": 9788, - "startIndex": 9779, - "textRun": { - "content": "Entry 49\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 9779 - }, - { - "endIndex": 9977, - "paragraph": { - "elements": [ - { - "endIndex": 9977, - "startIndex": 9788, - "textRun": { - "content": "This is the content paragraph for Entry 49. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 49.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 9788 - }, - { - "endIndex": 9986, - "paragraph": { - "elements": [ - { - "endIndex": 9986, - "startIndex": 9977, - "textRun": { - "content": "Entry 50\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 9977 - }, - { - "endIndex": 10175, - "paragraph": { - "elements": [ - { - "endIndex": 10175, - "startIndex": 9986, - "textRun": { - "content": "This is the content paragraph for Entry 50. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 50.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 9986 - }, - { - "endIndex": 10184, - "paragraph": { - "elements": [ - { - "endIndex": 10184, - "startIndex": 10175, - "textRun": { - "content": "Entry 51\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 10175 - }, - { - "endIndex": 10373, - "paragraph": { - "elements": [ - { - "endIndex": 10373, - "startIndex": 10184, - "textRun": { - "content": "This is the content paragraph for Entry 51. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 51.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 10184 - }, - { - "endIndex": 10382, - "paragraph": { - "elements": [ - { - "endIndex": 10382, - "startIndex": 10373, - "textRun": { - "content": "Entry 52\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 10373 - }, - { - "endIndex": 10571, - "paragraph": { - "elements": [ - { - "endIndex": 10571, - "startIndex": 10382, - "textRun": { - "content": "This is the content paragraph for Entry 52. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 52.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 10382 - }, - { - "endIndex": 10580, - "paragraph": { - "elements": [ - { - "endIndex": 10580, - "startIndex": 10571, - "textRun": { - "content": "Entry 53\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 10571 - }, - { - "endIndex": 10769, - "paragraph": { - "elements": [ - { - "endIndex": 10769, - "startIndex": 10580, - "textRun": { - "content": "This is the content paragraph for Entry 53. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 53.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 10580 - }, - { - "endIndex": 10778, - "paragraph": { - "elements": [ - { - "endIndex": 10778, - "startIndex": 10769, - "textRun": { - "content": "Entry 54\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 10769 - }, - { - "endIndex": 10967, - "paragraph": { - "elements": [ - { - "endIndex": 10967, - "startIndex": 10778, - "textRun": { - "content": "This is the content paragraph for Entry 54. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 54.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 10778 - }, - { - "endIndex": 10976, - "paragraph": { - "elements": [ - { - "endIndex": 10976, - "startIndex": 10967, - "textRun": { - "content": "Entry 55\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 10967 - }, - { - "endIndex": 11165, - "paragraph": { - "elements": [ - { - "endIndex": 11165, - "startIndex": 10976, - "textRun": { - "content": "This is the content paragraph for Entry 55. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 55.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 10976 - }, - { - "endIndex": 11174, - "paragraph": { - "elements": [ - { - "endIndex": 11174, - "startIndex": 11165, - "textRun": { - "content": "Entry 56\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 11165 - }, - { - "endIndex": 11363, - "paragraph": { - "elements": [ - { - "endIndex": 11363, - "startIndex": 11174, - "textRun": { - "content": "This is the content paragraph for Entry 56. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 56.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 11174 - }, - { - "endIndex": 11372, - "paragraph": { - "elements": [ - { - "endIndex": 11372, - "startIndex": 11363, - "textRun": { - "content": "Entry 57\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 11363 - }, - { - "endIndex": 11561, - "paragraph": { - "elements": [ - { - "endIndex": 11561, - "startIndex": 11372, - "textRun": { - "content": "This is the content paragraph for Entry 57. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 57.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 11372 - }, - { - "endIndex": 11570, - "paragraph": { - "elements": [ - { - "endIndex": 11570, - "startIndex": 11561, - "textRun": { - "content": "Entry 58\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 11561 - }, - { - "endIndex": 11759, - "paragraph": { - "elements": [ - { - "endIndex": 11759, - "startIndex": 11570, - "textRun": { - "content": "This is the content paragraph for Entry 58. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 58.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 11570 - }, - { - "endIndex": 11768, - "paragraph": { - "elements": [ - { - "endIndex": 11768, - "startIndex": 11759, - "textRun": { - "content": "Entry 59\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 11759 - }, - { - "endIndex": 11957, - "paragraph": { - "elements": [ - { - "endIndex": 11957, - "startIndex": 11768, - "textRun": { - "content": "This is the content paragraph for Entry 59. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 59.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 11768 - }, - { - "endIndex": 11966, - "paragraph": { - "elements": [ - { - "endIndex": 11966, - "startIndex": 11957, - "textRun": { - "content": "Entry 60\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 11957 - }, - { - "endIndex": 12155, - "paragraph": { - "elements": [ - { - "endIndex": 12155, - "startIndex": 11966, - "textRun": { - "content": "This is the content paragraph for Entry 60. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 60.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 11966 - }, - { - "endIndex": 12164, - "paragraph": { - "elements": [ - { - "endIndex": 12164, - "startIndex": 12155, - "textRun": { - "content": "Entry 61\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 12155 - }, - { - "endIndex": 12353, - "paragraph": { - "elements": [ - { - "endIndex": 12353, - "startIndex": 12164, - "textRun": { - "content": "This is the content paragraph for Entry 61. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 61.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 12164 - }, - { - "endIndex": 12362, - "paragraph": { - "elements": [ - { - "endIndex": 12362, - "startIndex": 12353, - "textRun": { - "content": "Entry 62\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 12353 - }, - { - "endIndex": 12551, - "paragraph": { - "elements": [ - { - "endIndex": 12551, - "startIndex": 12362, - "textRun": { - "content": "This is the content paragraph for Entry 62. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 62.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 12362 - }, - { - "endIndex": 12560, - "paragraph": { - "elements": [ - { - "endIndex": 12560, - "startIndex": 12551, - "textRun": { - "content": "Entry 63\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 12551 - }, - { - "endIndex": 12749, - "paragraph": { - "elements": [ - { - "endIndex": 12749, - "startIndex": 12560, - "textRun": { - "content": "This is the content paragraph for Entry 63. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 63.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 12560 - }, - { - "endIndex": 12758, - "paragraph": { - "elements": [ - { - "endIndex": 12758, - "startIndex": 12749, - "textRun": { - "content": "Entry 64\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 12749 - }, - { - "endIndex": 12947, - "paragraph": { - "elements": [ - { - "endIndex": 12947, - "startIndex": 12758, - "textRun": { - "content": "This is the content paragraph for Entry 64. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 64.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 12758 - }, - { - "endIndex": 12956, - "paragraph": { - "elements": [ - { - "endIndex": 12956, - "startIndex": 12947, - "textRun": { - "content": "Entry 65\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 12947 - }, - { - "endIndex": 13145, - "paragraph": { - "elements": [ - { - "endIndex": 13145, - "startIndex": 12956, - "textRun": { - "content": "This is the content paragraph for Entry 65. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 65.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 12956 - }, - { - "endIndex": 13154, - "paragraph": { - "elements": [ - { - "endIndex": 13154, - "startIndex": 13145, - "textRun": { - "content": "Entry 66\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 13145 - }, - { - "endIndex": 13343, - "paragraph": { - "elements": [ - { - "endIndex": 13343, - "startIndex": 13154, - "textRun": { - "content": "This is the content paragraph for Entry 66. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 66.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 13154 - }, - { - "endIndex": 13352, - "paragraph": { - "elements": [ - { - "endIndex": 13352, - "startIndex": 13343, - "textRun": { - "content": "Entry 67\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 13343 - }, - { - "endIndex": 13541, - "paragraph": { - "elements": [ - { - "endIndex": 13541, - "startIndex": 13352, - "textRun": { - "content": "This is the content paragraph for Entry 67. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 67.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 13352 - }, - { - "endIndex": 13550, - "paragraph": { - "elements": [ - { - "endIndex": 13550, - "startIndex": 13541, - "textRun": { - "content": "Entry 68\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 13541 - }, - { - "endIndex": 13739, - "paragraph": { - "elements": [ - { - "endIndex": 13739, - "startIndex": 13550, - "textRun": { - "content": "This is the content paragraph for Entry 68. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 68.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 13550 - }, - { - "endIndex": 13748, - "paragraph": { - "elements": [ - { - "endIndex": 13748, - "startIndex": 13739, - "textRun": { - "content": "Entry 69\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 13739 - }, - { - "endIndex": 13937, - "paragraph": { - "elements": [ - { - "endIndex": 13937, - "startIndex": 13748, - "textRun": { - "content": "This is the content paragraph for Entry 69. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 69.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 13748 - }, - { - "endIndex": 13946, - "paragraph": { - "elements": [ - { - "endIndex": 13946, - "startIndex": 13937, - "textRun": { - "content": "Entry 70\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 13937 - }, - { - "endIndex": 14135, - "paragraph": { - "elements": [ - { - "endIndex": 14135, - "startIndex": 13946, - "textRun": { - "content": "This is the content paragraph for Entry 70. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 70.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 13946 - }, - { - "endIndex": 14144, - "paragraph": { - "elements": [ - { - "endIndex": 14144, - "startIndex": 14135, - "textRun": { - "content": "Entry 71\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 14135 - }, - { - "endIndex": 14333, - "paragraph": { - "elements": [ - { - "endIndex": 14333, - "startIndex": 14144, - "textRun": { - "content": "This is the content paragraph for Entry 71. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 71.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 14144 - }, - { - "endIndex": 14342, - "paragraph": { - "elements": [ - { - "endIndex": 14342, - "startIndex": 14333, - "textRun": { - "content": "Entry 72\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 14333 - }, - { - "endIndex": 14531, - "paragraph": { - "elements": [ - { - "endIndex": 14531, - "startIndex": 14342, - "textRun": { - "content": "This is the content paragraph for Entry 72. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 72.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 14342 - }, - { - "endIndex": 14540, - "paragraph": { - "elements": [ - { - "endIndex": 14540, - "startIndex": 14531, - "textRun": { - "content": "Entry 73\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 14531 - }, - { - "endIndex": 14729, - "paragraph": { - "elements": [ - { - "endIndex": 14729, - "startIndex": 14540, - "textRun": { - "content": "This is the content paragraph for Entry 73. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 73.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 14540 - }, - { - "endIndex": 14738, - "paragraph": { - "elements": [ - { - "endIndex": 14738, - "startIndex": 14729, - "textRun": { - "content": "Entry 74\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 14729 - }, - { - "endIndex": 14927, - "paragraph": { - "elements": [ - { - "endIndex": 14927, - "startIndex": 14738, - "textRun": { - "content": "This is the content paragraph for Entry 74. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 74.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 14738 - }, - { - "endIndex": 14936, - "paragraph": { - "elements": [ - { - "endIndex": 14936, - "startIndex": 14927, - "textRun": { - "content": "Entry 75\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 14927 - }, - { - "endIndex": 15125, - "paragraph": { - "elements": [ - { - "endIndex": 15125, - "startIndex": 14936, - "textRun": { - "content": "This is the content paragraph for Entry 75. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 75.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 14936 - }, - { - "endIndex": 15134, - "paragraph": { - "elements": [ - { - "endIndex": 15134, - "startIndex": 15125, - "textRun": { - "content": "Entry 76\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 15125 - }, - { - "endIndex": 15323, - "paragraph": { - "elements": [ - { - "endIndex": 15323, - "startIndex": 15134, - "textRun": { - "content": "This is the content paragraph for Entry 76. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 76.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 15134 - }, - { - "endIndex": 15332, - "paragraph": { - "elements": [ - { - "endIndex": 15332, - "startIndex": 15323, - "textRun": { - "content": "Entry 77\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 15323 - }, - { - "endIndex": 15521, - "paragraph": { - "elements": [ - { - "endIndex": 15521, - "startIndex": 15332, - "textRun": { - "content": "This is the content paragraph for Entry 77. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 77.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 15332 - }, - { - "endIndex": 15530, - "paragraph": { - "elements": [ - { - "endIndex": 15530, - "startIndex": 15521, - "textRun": { - "content": "Entry 78\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 15521 - }, - { - "endIndex": 15719, - "paragraph": { - "elements": [ - { - "endIndex": 15719, - "startIndex": 15530, - "textRun": { - "content": "This is the content paragraph for Entry 78. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 78.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 15530 - }, - { - "endIndex": 15728, - "paragraph": { - "elements": [ - { - "endIndex": 15728, - "startIndex": 15719, - "textRun": { - "content": "Entry 79\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 15719 - }, - { - "endIndex": 15917, - "paragraph": { - "elements": [ - { - "endIndex": 15917, - "startIndex": 15728, - "textRun": { - "content": "This is the content paragraph for Entry 79. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 79.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 15728 - }, - { - "endIndex": 15926, - "paragraph": { - "elements": [ - { - "endIndex": 15926, - "startIndex": 15917, - "textRun": { - "content": "Entry 80\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 15917 - }, - { - "endIndex": 16115, - "paragraph": { - "elements": [ - { - "endIndex": 16115, - "startIndex": 15926, - "textRun": { - "content": "This is the content paragraph for Entry 80. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 80.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 15926 - }, - { - "endIndex": 16124, - "paragraph": { - "elements": [ - { - "endIndex": 16124, - "startIndex": 16115, - "textRun": { - "content": "Entry 81\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 16115 - }, - { - "endIndex": 16313, - "paragraph": { - "elements": [ - { - "endIndex": 16313, - "startIndex": 16124, - "textRun": { - "content": "This is the content paragraph for Entry 81. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 81.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 16124 - }, - { - "endIndex": 16322, - "paragraph": { - "elements": [ - { - "endIndex": 16322, - "startIndex": 16313, - "textRun": { - "content": "Entry 82\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 16313 - }, - { - "endIndex": 16511, - "paragraph": { - "elements": [ - { - "endIndex": 16511, - "startIndex": 16322, - "textRun": { - "content": "This is the content paragraph for Entry 82. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 82.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 16322 - }, - { - "endIndex": 16520, - "paragraph": { - "elements": [ - { - "endIndex": 16520, - "startIndex": 16511, - "textRun": { - "content": "Entry 83\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 16511 - }, - { - "endIndex": 16709, - "paragraph": { - "elements": [ - { - "endIndex": 16709, - "startIndex": 16520, - "textRun": { - "content": "This is the content paragraph for Entry 83. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 83.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 16520 - }, - { - "endIndex": 16718, - "paragraph": { - "elements": [ - { - "endIndex": 16718, - "startIndex": 16709, - "textRun": { - "content": "Entry 84\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 16709 - }, - { - "endIndex": 16907, - "paragraph": { - "elements": [ - { - "endIndex": 16907, - "startIndex": 16718, - "textRun": { - "content": "This is the content paragraph for Entry 84. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 84.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 16718 - }, - { - "endIndex": 16916, - "paragraph": { - "elements": [ - { - "endIndex": 16916, - "startIndex": 16907, - "textRun": { - "content": "Entry 85\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 16907 - }, - { - "endIndex": 17105, - "paragraph": { - "elements": [ - { - "endIndex": 17105, - "startIndex": 16916, - "textRun": { - "content": "This is the content paragraph for Entry 85. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 85.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 16916 - }, - { - "endIndex": 17114, - "paragraph": { - "elements": [ - { - "endIndex": 17114, - "startIndex": 17105, - "textRun": { - "content": "Entry 86\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 17105 - }, - { - "endIndex": 17303, - "paragraph": { - "elements": [ - { - "endIndex": 17303, - "startIndex": 17114, - "textRun": { - "content": "This is the content paragraph for Entry 86. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 86.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 17114 - }, - { - "endIndex": 17312, - "paragraph": { - "elements": [ - { - "endIndex": 17312, - "startIndex": 17303, - "textRun": { - "content": "Entry 87\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 17303 - }, - { - "endIndex": 17501, - "paragraph": { - "elements": [ - { - "endIndex": 17501, - "startIndex": 17312, - "textRun": { - "content": "This is the content paragraph for Entry 87. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 87.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 17312 - }, - { - "endIndex": 17510, - "paragraph": { - "elements": [ - { - "endIndex": 17510, - "startIndex": 17501, - "textRun": { - "content": "Entry 88\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 17501 - }, - { - "endIndex": 17699, - "paragraph": { - "elements": [ - { - "endIndex": 17699, - "startIndex": 17510, - "textRun": { - "content": "This is the content paragraph for Entry 88. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 88.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 17510 - }, - { - "endIndex": 17708, - "paragraph": { - "elements": [ - { - "endIndex": 17708, - "startIndex": 17699, - "textRun": { - "content": "Entry 89\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 17699 - }, - { - "endIndex": 17897, - "paragraph": { - "elements": [ - { - "endIndex": 17897, - "startIndex": 17708, - "textRun": { - "content": "This is the content paragraph for Entry 89. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 89.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 17708 - }, - { - "endIndex": 17906, - "paragraph": { - "elements": [ - { - "endIndex": 17906, - "startIndex": 17897, - "textRun": { - "content": "Entry 90\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 17897 - }, - { - "endIndex": 18095, - "paragraph": { - "elements": [ - { - "endIndex": 18095, - "startIndex": 17906, - "textRun": { - "content": "This is the content paragraph for Entry 90. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 90.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 17906 - }, - { - "endIndex": 18104, - "paragraph": { - "elements": [ - { - "endIndex": 18104, - "startIndex": 18095, - "textRun": { - "content": "Entry 91\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 18095 - }, - { - "endIndex": 18293, - "paragraph": { - "elements": [ - { - "endIndex": 18293, - "startIndex": 18104, - "textRun": { - "content": "This is the content paragraph for Entry 91. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 91.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 18104 - }, - { - "endIndex": 18302, - "paragraph": { - "elements": [ - { - "endIndex": 18302, - "startIndex": 18293, - "textRun": { - "content": "Entry 92\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 18293 - }, - { - "endIndex": 18491, - "paragraph": { - "elements": [ - { - "endIndex": 18491, - "startIndex": 18302, - "textRun": { - "content": "This is the content paragraph for Entry 92. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 92.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 18302 - }, - { - "endIndex": 18500, - "paragraph": { - "elements": [ - { - "endIndex": 18500, - "startIndex": 18491, - "textRun": { - "content": "Entry 93\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 18491 - }, - { - "endIndex": 18689, - "paragraph": { - "elements": [ - { - "endIndex": 18689, - "startIndex": 18500, - "textRun": { - "content": "This is the content paragraph for Entry 93. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 93.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 18500 - }, - { - "endIndex": 18698, - "paragraph": { - "elements": [ - { - "endIndex": 18698, - "startIndex": 18689, - "textRun": { - "content": "Entry 94\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 18689 - }, - { - "endIndex": 18887, - "paragraph": { - "elements": [ - { - "endIndex": 18887, - "startIndex": 18698, - "textRun": { - "content": "This is the content paragraph for Entry 94. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 94.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 18698 - }, - { - "endIndex": 18896, - "paragraph": { - "elements": [ - { - "endIndex": 18896, - "startIndex": 18887, - "textRun": { - "content": "Entry 95\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 18887 - }, - { - "endIndex": 19085, - "paragraph": { - "elements": [ - { - "endIndex": 19085, - "startIndex": 18896, - "textRun": { - "content": "This is the content paragraph for Entry 95. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 95.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 18896 - }, - { - "endIndex": 19094, - "paragraph": { - "elements": [ - { - "endIndex": 19094, - "startIndex": 19085, - "textRun": { - "content": "Entry 96\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 19085 - }, - { - "endIndex": 19283, - "paragraph": { - "elements": [ - { - "endIndex": 19283, - "startIndex": 19094, - "textRun": { - "content": "This is the content paragraph for Entry 96. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 96.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 19094 - }, - { - "endIndex": 19292, - "paragraph": { - "elements": [ - { - "endIndex": 19292, - "startIndex": 19283, - "textRun": { - "content": "Entry 97\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 19283 - }, - { - "endIndex": 19481, - "paragraph": { - "elements": [ - { - "endIndex": 19481, - "startIndex": 19292, - "textRun": { - "content": "This is the content paragraph for Entry 97. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 97.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 19292 - }, - { - "endIndex": 19490, - "paragraph": { - "elements": [ - { - "endIndex": 19490, - "startIndex": 19481, - "textRun": { - "content": "Entry 98\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 19481 - }, - { - "endIndex": 19679, - "paragraph": { - "elements": [ - { - "endIndex": 19679, - "startIndex": 19490, - "textRun": { - "content": "This is the content paragraph for Entry 98. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 98.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 19490 - }, - { - "endIndex": 19688, - "paragraph": { - "elements": [ - { - "endIndex": 19688, - "startIndex": 19679, - "textRun": { - "content": "Entry 99\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 19679 - }, - { - "endIndex": 19877, - "paragraph": { - "elements": [ - { - "endIndex": 19877, - "startIndex": 19688, - "textRun": { - "content": "This is the content paragraph for Entry 99. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 99.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 19688 - }, - { - "endIndex": 19887, - "paragraph": { - "elements": [ - { - "endIndex": 19887, - "startIndex": 19877, - "textRun": { - "content": "Entry 100\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 19877 - }, - { - "endIndex": 20078, - "paragraph": { - "elements": [ - { - "endIndex": 20078, - "startIndex": 19887, - "textRun": { - "content": "This is the content paragraph for Entry 100. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 100.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 19887 - }, - { - "endIndex": 20088, - "paragraph": { - "elements": [ - { - "endIndex": 20088, - "startIndex": 20078, - "textRun": { - "content": "Entry 101\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 20078 - }, - { - "endIndex": 20279, - "paragraph": { - "elements": [ - { - "endIndex": 20279, - "startIndex": 20088, - "textRun": { - "content": "This is the content paragraph for Entry 101. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 101.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 20088 - }, - { - "endIndex": 20289, - "paragraph": { - "elements": [ - { - "endIndex": 20289, - "startIndex": 20279, - "textRun": { - "content": "Entry 102\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 20279 - }, - { - "endIndex": 20480, - "paragraph": { - "elements": [ - { - "endIndex": 20480, - "startIndex": 20289, - "textRun": { - "content": "This is the content paragraph for Entry 102. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 102.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 20289 - }, - { - "endIndex": 20490, - "paragraph": { - "elements": [ - { - "endIndex": 20490, - "startIndex": 20480, - "textRun": { - "content": "Entry 103\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 20480 - }, - { - "endIndex": 20681, - "paragraph": { - "elements": [ - { - "endIndex": 20681, - "startIndex": 20490, - "textRun": { - "content": "This is the content paragraph for Entry 103. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 103.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 20490 - }, - { - "endIndex": 20691, - "paragraph": { - "elements": [ - { - "endIndex": 20691, - "startIndex": 20681, - "textRun": { - "content": "Entry 104\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 20681 - }, - { - "endIndex": 20882, - "paragraph": { - "elements": [ - { - "endIndex": 20882, - "startIndex": 20691, - "textRun": { - "content": "This is the content paragraph for Entry 104. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 104.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 20691 - }, - { - "endIndex": 20892, - "paragraph": { - "elements": [ - { - "endIndex": 20892, - "startIndex": 20882, - "textRun": { - "content": "Entry 105\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 20882 - }, - { - "endIndex": 21083, - "paragraph": { - "elements": [ - { - "endIndex": 21083, - "startIndex": 20892, - "textRun": { - "content": "This is the content paragraph for Entry 105. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 105.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 20892 - }, - { - "endIndex": 21093, - "paragraph": { - "elements": [ - { - "endIndex": 21093, - "startIndex": 21083, - "textRun": { - "content": "Entry 106\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 21083 - }, - { - "endIndex": 21284, - "paragraph": { - "elements": [ - { - "endIndex": 21284, - "startIndex": 21093, - "textRun": { - "content": "This is the content paragraph for Entry 106. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 106.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 21093 - }, - { - "endIndex": 21294, - "paragraph": { - "elements": [ - { - "endIndex": 21294, - "startIndex": 21284, - "textRun": { - "content": "Entry 107\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 21284 - }, - { - "endIndex": 21485, - "paragraph": { - "elements": [ - { - "endIndex": 21485, - "startIndex": 21294, - "textRun": { - "content": "This is the content paragraph for Entry 107. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 107.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 21294 - }, - { - "endIndex": 21495, - "paragraph": { - "elements": [ - { - "endIndex": 21495, - "startIndex": 21485, - "textRun": { - "content": "Entry 108\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 21485 - }, - { - "endIndex": 21686, - "paragraph": { - "elements": [ - { - "endIndex": 21686, - "startIndex": 21495, - "textRun": { - "content": "This is the content paragraph for Entry 108. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 108.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 21495 - }, - { - "endIndex": 21696, - "paragraph": { - "elements": [ - { - "endIndex": 21696, - "startIndex": 21686, - "textRun": { - "content": "Entry 109\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 21686 - }, - { - "endIndex": 21887, - "paragraph": { - "elements": [ - { - "endIndex": 21887, - "startIndex": 21696, - "textRun": { - "content": "This is the content paragraph for Entry 109. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 109.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 21696 - }, - { - "endIndex": 21897, - "paragraph": { - "elements": [ - { - "endIndex": 21897, - "startIndex": 21887, - "textRun": { - "content": "Entry 110\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 21887 - }, - { - "endIndex": 22088, - "paragraph": { - "elements": [ - { - "endIndex": 22088, - "startIndex": 21897, - "textRun": { - "content": "This is the content paragraph for Entry 110. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 110.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 21897 - }, - { - "endIndex": 22098, - "paragraph": { - "elements": [ - { - "endIndex": 22098, - "startIndex": 22088, - "textRun": { - "content": "Entry 111\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 22088 - }, - { - "endIndex": 22289, - "paragraph": { - "elements": [ - { - "endIndex": 22289, - "startIndex": 22098, - "textRun": { - "content": "This is the content paragraph for Entry 111. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 111.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 22098 - }, - { - "endIndex": 22299, - "paragraph": { - "elements": [ - { - "endIndex": 22299, - "startIndex": 22289, - "textRun": { - "content": "Entry 112\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 22289 - }, - { - "endIndex": 22490, - "paragraph": { - "elements": [ - { - "endIndex": 22490, - "startIndex": 22299, - "textRun": { - "content": "This is the content paragraph for Entry 112. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 112.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 22299 - }, - { - "endIndex": 22500, - "paragraph": { - "elements": [ - { - "endIndex": 22500, - "startIndex": 22490, - "textRun": { - "content": "Entry 113\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 22490 - }, - { - "endIndex": 22691, - "paragraph": { - "elements": [ - { - "endIndex": 22691, - "startIndex": 22500, - "textRun": { - "content": "This is the content paragraph for Entry 113. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 113.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 22500 - }, - { - "endIndex": 22701, - "paragraph": { - "elements": [ - { - "endIndex": 22701, - "startIndex": 22691, - "textRun": { - "content": "Entry 114\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 22691 - }, - { - "endIndex": 22892, - "paragraph": { - "elements": [ - { - "endIndex": 22892, - "startIndex": 22701, - "textRun": { - "content": "This is the content paragraph for Entry 114. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 114.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 22701 - }, - { - "endIndex": 22902, - "paragraph": { - "elements": [ - { - "endIndex": 22902, - "startIndex": 22892, - "textRun": { - "content": "Entry 115\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 22892 - }, - { - "endIndex": 23093, - "paragraph": { - "elements": [ - { - "endIndex": 23093, - "startIndex": 22902, - "textRun": { - "content": "This is the content paragraph for Entry 115. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 115.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 22902 - }, - { - "endIndex": 23103, - "paragraph": { - "elements": [ - { - "endIndex": 23103, - "startIndex": 23093, - "textRun": { - "content": "Entry 116\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 23093 - }, - { - "endIndex": 23294, - "paragraph": { - "elements": [ - { - "endIndex": 23294, - "startIndex": 23103, - "textRun": { - "content": "This is the content paragraph for Entry 116. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 116.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 23103 - }, - { - "endIndex": 23304, - "paragraph": { - "elements": [ - { - "endIndex": 23304, - "startIndex": 23294, - "textRun": { - "content": "Entry 117\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 23294 - }, - { - "endIndex": 23495, - "paragraph": { - "elements": [ - { - "endIndex": 23495, - "startIndex": 23304, - "textRun": { - "content": "This is the content paragraph for Entry 117. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 117.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 23304 - }, - { - "endIndex": 23505, - "paragraph": { - "elements": [ - { - "endIndex": 23505, - "startIndex": 23495, - "textRun": { - "content": "Entry 118\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 23495 - }, - { - "endIndex": 23696, - "paragraph": { - "elements": [ - { - "endIndex": 23696, - "startIndex": 23505, - "textRun": { - "content": "This is the content paragraph for Entry 118. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 118.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 23505 - }, - { - "endIndex": 23706, - "paragraph": { - "elements": [ - { - "endIndex": 23706, - "startIndex": 23696, - "textRun": { - "content": "Entry 119\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 23696 - }, - { - "endIndex": 23897, - "paragraph": { - "elements": [ - { - "endIndex": 23897, - "startIndex": 23706, - "textRun": { - "content": "This is the content paragraph for Entry 119. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 119.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 23706 - }, - { - "endIndex": 23907, - "paragraph": { - "elements": [ - { - "endIndex": 23907, - "startIndex": 23897, - "textRun": { - "content": "Entry 120\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 23897 - }, - { - "endIndex": 24098, - "paragraph": { - "elements": [ - { - "endIndex": 24098, - "startIndex": 23907, - "textRun": { - "content": "This is the content paragraph for Entry 120. It contains a few sentences of text meant to simulate real document data. The quick brown fox jumps over the lazy dog \u2014 example content line 120.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 23907 - }, - { - "endIndex": 24115, - "paragraph": { - "elements": [ - { - "endIndex": 24115, - "startIndex": 24098, - "textRun": { - "content": "Expected Outcome\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 24098 - }, - { - "endIndex": 24328, - "paragraph": { - "elements": [ - { - "endIndex": 24328, - "startIndex": 24115, - "textRun": { - "content": "The importer should successfully process all 120 entries without timing out or exceeding API rate limits. If rate limiting occurs, retries should be handled gracefully, and all entries should import successfully.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 24115 - } - ] - }, - "documentStyle": { - "background": { - "color": {} - }, - "documentFormat": { - "documentMode": "PAGES" - }, - "marginBottom": { - "magnitude": 72, - "unit": "PT" - }, - "marginFooter": { - "magnitude": 36, - "unit": "PT" - }, - "marginHeader": { - "magnitude": 36, - "unit": "PT" - }, - "marginLeft": { - "magnitude": 90, - "unit": "PT" - }, - "marginRight": { - "magnitude": 90, - "unit": "PT" - }, - "marginTop": { - "magnitude": 72, - "unit": "PT" - }, - "pageNumberStart": 1, - "pageSize": { - "height": { - "magnitude": 792, - "unit": "PT" - }, - "width": { - "magnitude": 612, - "unit": "PT" - } - }, - "useCustomHeaderFooterMargins": true - }, - "lists": { - "kix.list.1": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Noto Sans Symbols", - "weight": 400 - } - } - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.2": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Noto Sans Symbols", - "weight": 400 - } - } - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.3": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 54, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Noto Sans Symbols", - "weight": 400 - } - } - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.4": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Noto Sans Symbols", - "weight": 400 - } - } - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.5": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.6": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.7": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 54, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.8": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.9": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "magnitude": 72, - "unit": "PT" - }, - "indentStart": { - "magnitude": 90, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - } - }, - "namedStyles": { - "styles": [ - { - "namedStyleType": "NORMAL_TEXT", - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": true, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 115, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 10, - "unit": "PT" - }, - "spacingMode": "NEVER_COLLAPSE" - }, - "textStyle": { - "backgroundColor": {}, - "baselineOffset": "NONE", - "bold": false, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "italic": false, - "smallCaps": false, - "strikethrough": false, - "underline": false, - "weightedFontFamily": { - "fontFamily": "Cambria", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_1", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 24, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "bold": true, - "fontSize": { - "magnitude": 14, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.5686275, - "green": 0.3764706, - "red": 0.21176471 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_2", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "bold": true, - "fontSize": { - "magnitude": 13, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_3", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "bold": true, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_4", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "bold": true, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "italic": true, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_5", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.38039216, - "green": 0.24705882, - "red": 0.14117648 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_6", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.38039216, - "green": 0.24705882, - "red": 0.14117648 - } - } - }, - "italic": true, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "TITLE", - "paragraphStyle": { - "borderBottom": { - "color": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "dashStyle": "SOLID", - "padding": { - "magnitude": 4, - "unit": "PT" - }, - "width": { - "magnitude": 1, - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT", - "spaceBelow": { - "magnitude": 15, - "unit": "PT" - }, - "spacingMode": "NEVER_COLLAPSE" - }, - "textStyle": { - "fontSize": { - "magnitude": 26, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.3647059, - "green": 0.21176471, - "red": 0.09019608 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "SUBTITLE", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - }, - "textStyle": { - "fontSize": { - "magnitude": 12, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "italic": true, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - } - ] - } - }, - "tabProperties": { - "index": 0, - "tabId": "t.0", - "title": "Tab 1" - } - } - ], - "title": "Doc_5_Bulk_Entry_Stress_Test" -} \ No newline at end of file diff --git a/apps/google-docs/src/utils/test_docs_json/Doc_6_Multilingual_Test.json b/apps/google-docs/src/utils/test_docs_json/Doc_6_Multilingual_Test.json deleted file mode 100644 index 2ff02e069e..0000000000 --- a/apps/google-docs/src/utils/test_docs_json/Doc_6_Multilingual_Test.json +++ /dev/null @@ -1,1875 +0,0 @@ -{ - "documentId": "1a4tbal0Xva7ezmEslgQCZGUMC15OFRqh-XQEBbyWGlU", - "suggestionsViewMode": "PREVIEW_WITHOUT_SUGGESTIONS", - "tabs": [ - { - "documentTab": { - "body": { - "content": [ - { - "endIndex": 1, - "sectionBreak": { - "sectionStyle": { - "columnSeparatorStyle": "NONE", - "contentDirection": "LEFT_TO_RIGHT", - "sectionType": "CONTINUOUS" - } - } - }, - { - "endIndex": 37, - "paragraph": { - "elements": [ - { - "endIndex": 37, - "startIndex": 1, - "textRun": { - "content": "Document Import \u2014 Multilingual Test\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_1" - } - }, - "startIndex": 1 - }, - { - "endIndex": 46, - "paragraph": { - "elements": [ - { - "endIndex": 46, - "startIndex": 37, - "textRun": { - "content": "Overview\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 37 - }, - { - "endIndex": 327, - "paragraph": { - "elements": [ - { - "endIndex": 327, - "startIndex": 46, - "textRun": { - "content": "This document tests the importer's ability to handle multiple languages and encodings within a single document. It includes content in English, French, Spanish, Japanese, and Arabic. The importer must preserve text direction, special characters, and Unicode formatting accurately.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 46 - }, - { - "endIndex": 343, - "paragraph": { - "elements": [ - { - "endIndex": 343, - "startIndex": 327, - "textRun": { - "content": "English Section\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 327 - }, - { - "endIndex": 427, - "paragraph": { - "elements": [ - { - "endIndex": 427, - "startIndex": 343, - "textRun": { - "content": "Hello! This is an English paragraph meant to validate basic Latin character import.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 343 - }, - { - "endIndex": 445, - "paragraph": { - "elements": [ - { - "endIndex": 445, - "startIndex": 427, - "textRun": { - "content": "Section Fran\u00e7aise\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 427 - }, - { - "endIndex": 555, - "paragraph": { - "elements": [ - { - "endIndex": 555, - "startIndex": 445, - "textRun": { - "content": "Bonjour ! Ceci est un paragraphe en fran\u00e7ais pour valider l'importation des caract\u00e8res accentu\u00e9s et sp\u00e9ciaux.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 445 - }, - { - "endIndex": 574, - "paragraph": { - "elements": [ - { - "endIndex": 574, - "startIndex": 555, - "textRun": { - "content": "Secci\u00f3n en Espa\u00f1ol\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 555 - }, - { - "endIndex": 673, - "paragraph": { - "elements": [ - { - "endIndex": 673, - "startIndex": 574, - "textRun": { - "content": "\u00a1Hola! Este es un p\u00e1rrafo en espa\u00f1ol con signos de exclamaci\u00f3n invertidos y caracteres como \u00f1 y \u00e1.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 574 - }, - { - "endIndex": 683, - "paragraph": { - "elements": [ - { - "endIndex": 683, - "startIndex": 673, - "textRun": { - "content": "\u65e5\u672c\u8a9e\u306e\u30bb\u30af\u30b7\u30e7\u30f3\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 673 - }, - { - "endIndex": 733, - "paragraph": { - "elements": [ - { - "endIndex": 733, - "startIndex": 683, - "textRun": { - "content": "\u3053\u3093\u306b\u3061\u306f\uff01\u3053\u308c\u306f\u65e5\u672c\u8a9e\u306e\u6bb5\u843d\u3067\u3001\u30a4\u30f3\u30dd\u30fc\u30c8\u6642\u306bUnicode\u306e\u6b63\u78ba\u6027\u3092\u78ba\u8a8d\u3059\u308b\u305f\u3081\u306e\u30c6\u30b9\u30c8\u3067\u3059\u3002\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 683 - }, - { - "endIndex": 746, - "paragraph": { - "elements": [ - { - "endIndex": 746, - "startIndex": 733, - "textRun": { - "content": "\u0627\u0644\u0642\u0633\u0645 \u0627\u0644\u0639\u0631\u0628\u064a\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 733 - }, - { - "endIndex": 835, - "paragraph": { - "elements": [ - { - "endIndex": 835, - "startIndex": 746, - "textRun": { - "content": "\u0645\u0631\u062d\u0628\u064b\u0627! \u0647\u0630\u0647 \u0641\u0642\u0631\u0629 \u0628\u0627\u0644\u0644\u063a\u0629 \u0627\u0644\u0639\u0631\u0628\u064a\u0629 \u0644\u0644\u062a\u062d\u0642\u0642 \u0645\u0646 \u062f\u0639\u0645 \u0627\u0644\u0646\u0635 \u0645\u0646 \u0627\u0644\u064a\u0645\u064a\u0646 \u0625\u0644\u0649 \u0627\u0644\u064a\u0633\u0627\u0631 \u0623\u062b\u0646\u0627\u0621 \u0627\u0644\u0627\u0633\u062a\u064a\u0631\u0627\u062f.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 746 - }, - { - "endIndex": 858, - "paragraph": { - "elements": [ - { - "endIndex": 858, - "startIndex": 835, - "textRun": { - "content": "Mixed Language Example\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 835 - }, - { - "endIndex": 1021, - "paragraph": { - "elements": [ - { - "endIndex": 1021, - "startIndex": 858, - "textRun": { - "content": "This paragraph mixes languages: Bonjour, this is an English-French hybrid. \u00a1Hola! \u3053\u3093\u306b\u3061\u306f! \u0645\u0631\u062d\u0628\u064b\u0627! Each script should maintain its formatting and display correctly.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 858 - }, - { - "endIndex": 1038, - "paragraph": { - "elements": [ - { - "endIndex": 1038, - "startIndex": 1021, - "textRun": { - "content": "Expected Outcome\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 1021 - }, - { - "endIndex": 1266, - "paragraph": { - "elements": [ - { - "endIndex": 1266, - "startIndex": 1038, - "textRun": { - "content": "All multilingual content should appear correctly encoded and displayed. The importer must support both left-to-right and right-to-left scripts without data loss, ensuring that accented characters and Unicode text are preserved.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 1038 - } - ] - }, - "documentStyle": { - "background": { - "color": {} - }, - "documentFormat": { - "documentMode": "PAGES" - }, - "marginBottom": { - "magnitude": 72, - "unit": "PT" - }, - "marginFooter": { - "magnitude": 36, - "unit": "PT" - }, - "marginHeader": { - "magnitude": 36, - "unit": "PT" - }, - "marginLeft": { - "magnitude": 90, - "unit": "PT" - }, - "marginRight": { - "magnitude": 90, - "unit": "PT" - }, - "marginTop": { - "magnitude": 72, - "unit": "PT" - }, - "pageNumberStart": 1, - "pageSize": { - "height": { - "magnitude": 792, - "unit": "PT" - }, - "width": { - "magnitude": 612, - "unit": "PT" - } - }, - "useCustomHeaderFooterMargins": true - }, - "lists": { - "kix.list.1": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Noto Sans Symbols", - "weight": 400 - } - } - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.2": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Noto Sans Symbols", - "weight": 400 - } - } - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.3": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 54, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Noto Sans Symbols", - "weight": 400 - } - } - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.4": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Noto Sans Symbols", - "weight": 400 - } - } - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.5": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.6": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.7": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 54, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.8": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.9": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "magnitude": 72, - "unit": "PT" - }, - "indentStart": { - "magnitude": 90, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - } - }, - "namedStyles": { - "styles": [ - { - "namedStyleType": "NORMAL_TEXT", - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": true, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 115, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 10, - "unit": "PT" - }, - "spacingMode": "NEVER_COLLAPSE" - }, - "textStyle": { - "backgroundColor": {}, - "baselineOffset": "NONE", - "bold": false, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "italic": false, - "smallCaps": false, - "strikethrough": false, - "underline": false, - "weightedFontFamily": { - "fontFamily": "Cambria", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_1", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 24, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "bold": true, - "fontSize": { - "magnitude": 14, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.5686275, - "green": 0.3764706, - "red": 0.21176471 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_2", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "bold": true, - "fontSize": { - "magnitude": 13, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_3", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "bold": true, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_4", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "bold": true, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "italic": true, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_5", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.38039216, - "green": 0.24705882, - "red": 0.14117648 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_6", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.38039216, - "green": 0.24705882, - "red": 0.14117648 - } - } - }, - "italic": true, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "TITLE", - "paragraphStyle": { - "borderBottom": { - "color": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "dashStyle": "SOLID", - "padding": { - "magnitude": 4, - "unit": "PT" - }, - "width": { - "magnitude": 1, - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT", - "spaceBelow": { - "magnitude": 15, - "unit": "PT" - }, - "spacingMode": "NEVER_COLLAPSE" - }, - "textStyle": { - "fontSize": { - "magnitude": 26, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.3647059, - "green": 0.21176471, - "red": 0.09019608 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "SUBTITLE", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - }, - "textStyle": { - "fontSize": { - "magnitude": 12, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "italic": true, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - } - ] - } - }, - "tabProperties": { - "index": 0, - "tabId": "t.0", - "title": "Tab 1" - } - } - ], - "title": "Doc_6_Multilingual_Test" -} \ No newline at end of file diff --git a/apps/google-docs/src/utils/test_docs_json/Doc_7_Edge_Cases_Test.json b/apps/google-docs/src/utils/test_docs_json/Doc_7_Edge_Cases_Test.json deleted file mode 100644 index b7c5178502..0000000000 --- a/apps/google-docs/src/utils/test_docs_json/Doc_7_Edge_Cases_Test.json +++ /dev/null @@ -1,5063 +0,0 @@ -{ - "documentId": "1kL_humdui_KBfXbKj5bkTM29MjGRfykHPdZBz5TbZrE", - "suggestionsViewMode": "PREVIEW_WITHOUT_SUGGESTIONS", - "tabs": [ - { - "documentTab": { - "body": { - "content": [ - { - "endIndex": 1, - "sectionBreak": { - "sectionStyle": { - "columnSeparatorStyle": "NONE", - "contentDirection": "LEFT_TO_RIGHT", - "sectionType": "CONTINUOUS" - } - } - }, - { - "endIndex": 35, - "paragraph": { - "elements": [ - { - "endIndex": 35, - "startIndex": 1, - "textRun": { - "content": "Document Import \u2014 Edge Cases Test\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_1" - } - }, - "startIndex": 1 - }, - { - "endIndex": 44, - "paragraph": { - "elements": [ - { - "endIndex": 44, - "startIndex": 35, - "textRun": { - "content": "Overview\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 35 - }, - { - "endIndex": 290, - "paragraph": { - "elements": [ - { - "endIndex": 290, - "startIndex": 44, - "textRun": { - "content": "This document is designed to test the importer\u2019s behavior with unusual or problematic inputs, including empty content, malformed structures, and special characters. The goal is to ensure the importer handles edge cases gracefully without errors.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 44 - }, - { - "endIndex": 316, - "paragraph": { - "elements": [ - { - "endIndex": 316, - "startIndex": 290, - "textRun": { - "content": "Malformed Content Example\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 290 - }, - { - "endIndex": 411, - "paragraph": { - "elements": [ - { - "endIndex": 411, - "startIndex": 316, - "textRun": { - "content": "This section contains intentionally malformed list and table examples to test error tolerance.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 316 - }, - { - "endIndex": 459, - "paragraph": { - "elements": [ - { - "endIndex": 459, - "startIndex": 411, - "textRun": { - "content": "Improper list start without bullets or numbers:\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 411 - }, - { - "endIndex": 474, - "paragraph": { - "elements": [ - { - "endIndex": 474, - "startIndex": 459, - "textRun": { - "content": "- - - Item one\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 459 - }, - { - "endIndex": 487, - "paragraph": { - "elements": [ - { - "endIndex": 487, - "startIndex": 474, - "textRun": { - "content": "1.. Item two\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 474 - }, - { - "endIndex": 518, - "paragraph": { - "elements": [ - { - "endIndex": 518, - "startIndex": 487, - "textRun": { - "content": "\u2022 Mixed bullet character usage\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 487 - }, - { - "endIndex": 556, - "paragraph": { - "elements": [ - { - "endIndex": 556, - "startIndex": 518, - "textRun": { - "content": "Malformed table structure simulation:\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 518 - }, - { - "endIndex": 580, - "paragraph": { - "elements": [ - { - "endIndex": 580, - "startIndex": 556, - "textRun": { - "content": "| Header 1 | Header 2 |\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 556 - }, - { - "endIndex": 627, - "paragraph": { - "elements": [ - { - "endIndex": 627, - "startIndex": 580, - "textRun": { - "content": "| Value 1 | Value 2 | Value 3 (extra column) |\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 580 - }, - { - "endIndex": 645, - "paragraph": { - "elements": [ - { - "endIndex": 645, - "startIndex": 627, - "textRun": { - "content": "| Only one cell |\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 627 - }, - { - "endIndex": 646, - "paragraph": { - "elements": [ - { - "endIndex": 646, - "startIndex": 645, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 645 - }, - { - "endIndex": 700, - "startIndex": 646, - "table": { - "columns": 6, - "rows": 4, - "tableRows": [ - { - "endIndex": 660, - "startIndex": 647, - "tableCells": [ - { - "content": [ - { - "endIndex": 650, - "paragraph": { - "elements": [ - { - "endIndex": 650, - "startIndex": 649, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": false, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "spacingMode": "COLLAPSE_LISTS" - } - }, - "startIndex": 649 - } - ], - "endIndex": 650, - "startIndex": 648, - "tableCellStyle": { - "backgroundColor": {}, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - }, - { - "content": [ - { - "endIndex": 652, - "paragraph": { - "elements": [ - { - "endIndex": 652, - "startIndex": 651, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": false, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "spacingMode": "COLLAPSE_LISTS" - } - }, - "startIndex": 651 - } - ], - "endIndex": 652, - "startIndex": 650, - "tableCellStyle": { - "backgroundColor": {}, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - }, - { - "content": [ - { - "endIndex": 654, - "paragraph": { - "elements": [ - { - "endIndex": 654, - "startIndex": 653, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": false, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "spacingMode": "COLLAPSE_LISTS" - } - }, - "startIndex": 653 - } - ], - "endIndex": 654, - "startIndex": 652, - "tableCellStyle": { - "backgroundColor": {}, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - }, - { - "content": [ - { - "endIndex": 656, - "paragraph": { - "elements": [ - { - "endIndex": 656, - "startIndex": 655, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": false, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "spacingMode": "COLLAPSE_LISTS" - } - }, - "startIndex": 655 - } - ], - "endIndex": 656, - "startIndex": 654, - "tableCellStyle": { - "backgroundColor": {}, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - }, - { - "content": [ - { - "endIndex": 658, - "paragraph": { - "elements": [ - { - "endIndex": 658, - "startIndex": 657, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": false, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "spacingMode": "COLLAPSE_LISTS" - } - }, - "startIndex": 657 - } - ], - "endIndex": 658, - "startIndex": 656, - "tableCellStyle": { - "backgroundColor": {}, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - }, - { - "content": [ - { - "endIndex": 660, - "paragraph": { - "elements": [ - { - "endIndex": 660, - "startIndex": 659, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": false, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "spacingMode": "COLLAPSE_LISTS" - } - }, - "startIndex": 659 - } - ], - "endIndex": 660, - "startIndex": 658, - "tableCellStyle": { - "backgroundColor": {}, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - } - ], - "tableRowStyle": { - "minRowHeight": { - "unit": "PT" - } - } - }, - { - "endIndex": 673, - "startIndex": 660, - "tableCells": [ - { - "content": [ - { - "endIndex": 663, - "paragraph": { - "elements": [ - { - "endIndex": 663, - "startIndex": 662, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": false, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "spacingMode": "COLLAPSE_LISTS" - } - }, - "startIndex": 662 - } - ], - "endIndex": 663, - "startIndex": 661, - "tableCellStyle": { - "backgroundColor": {}, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - }, - { - "content": [ - { - "endIndex": 665, - "paragraph": { - "elements": [ - { - "endIndex": 665, - "startIndex": 664, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": false, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "spacingMode": "COLLAPSE_LISTS" - } - }, - "startIndex": 664 - } - ], - "endIndex": 665, - "startIndex": 663, - "tableCellStyle": { - "backgroundColor": {}, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - }, - { - "content": [ - { - "endIndex": 667, - "paragraph": { - "elements": [ - { - "endIndex": 667, - "startIndex": 666, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": false, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "spacingMode": "COLLAPSE_LISTS" - } - }, - "startIndex": 666 - } - ], - "endIndex": 667, - "startIndex": 665, - "tableCellStyle": { - "backgroundColor": {}, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - }, - { - "content": [ - { - "endIndex": 669, - "paragraph": { - "elements": [ - { - "endIndex": 669, - "startIndex": 668, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": false, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "spacingMode": "COLLAPSE_LISTS" - } - }, - "startIndex": 668 - } - ], - "endIndex": 669, - "startIndex": 667, - "tableCellStyle": { - "backgroundColor": {}, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - }, - { - "content": [ - { - "endIndex": 671, - "paragraph": { - "elements": [ - { - "endIndex": 671, - "startIndex": 670, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": false, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "spacingMode": "COLLAPSE_LISTS" - } - }, - "startIndex": 670 - } - ], - "endIndex": 671, - "startIndex": 669, - "tableCellStyle": { - "backgroundColor": {}, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - }, - { - "content": [ - { - "endIndex": 673, - "paragraph": { - "elements": [ - { - "endIndex": 673, - "startIndex": 672, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": false, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "spacingMode": "COLLAPSE_LISTS" - } - }, - "startIndex": 672 - } - ], - "endIndex": 673, - "startIndex": 671, - "tableCellStyle": { - "backgroundColor": {}, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - } - ], - "tableRowStyle": { - "minRowHeight": { - "unit": "PT" - } - } - }, - { - "endIndex": 686, - "startIndex": 673, - "tableCells": [ - { - "content": [ - { - "endIndex": 676, - "paragraph": { - "elements": [ - { - "endIndex": 676, - "startIndex": 675, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": false, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "spacingMode": "COLLAPSE_LISTS" - } - }, - "startIndex": 675 - } - ], - "endIndex": 676, - "startIndex": 674, - "tableCellStyle": { - "backgroundColor": {}, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - }, - { - "content": [ - { - "endIndex": 678, - "paragraph": { - "elements": [ - { - "endIndex": 678, - "startIndex": 677, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": false, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "spacingMode": "COLLAPSE_LISTS" - } - }, - "startIndex": 677 - } - ], - "endIndex": 678, - "startIndex": 676, - "tableCellStyle": { - "backgroundColor": {}, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - }, - { - "content": [ - { - "endIndex": 680, - "paragraph": { - "elements": [ - { - "endIndex": 680, - "startIndex": 679, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": false, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "spacingMode": "COLLAPSE_LISTS" - } - }, - "startIndex": 679 - } - ], - "endIndex": 680, - "startIndex": 678, - "tableCellStyle": { - "backgroundColor": {}, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - }, - { - "content": [ - { - "endIndex": 682, - "paragraph": { - "elements": [ - { - "endIndex": 682, - "startIndex": 681, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": false, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "spacingMode": "COLLAPSE_LISTS" - } - }, - "startIndex": 681 - } - ], - "endIndex": 682, - "startIndex": 680, - "tableCellStyle": { - "backgroundColor": {}, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - }, - { - "content": [ - { - "endIndex": 684, - "paragraph": { - "elements": [ - { - "endIndex": 684, - "startIndex": 683, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": false, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "spacingMode": "COLLAPSE_LISTS" - } - }, - "startIndex": 683 - } - ], - "endIndex": 684, - "startIndex": 682, - "tableCellStyle": { - "backgroundColor": {}, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - }, - { - "content": [ - { - "endIndex": 686, - "paragraph": { - "elements": [ - { - "endIndex": 686, - "startIndex": 685, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": false, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "spacingMode": "COLLAPSE_LISTS" - } - }, - "startIndex": 685 - } - ], - "endIndex": 686, - "startIndex": 684, - "tableCellStyle": { - "backgroundColor": {}, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - } - ], - "tableRowStyle": { - "minRowHeight": { - "unit": "PT" - } - } - }, - { - "endIndex": 699, - "startIndex": 686, - "tableCells": [ - { - "content": [ - { - "endIndex": 689, - "paragraph": { - "elements": [ - { - "endIndex": 689, - "startIndex": 688, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": false, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "spacingMode": "COLLAPSE_LISTS" - } - }, - "startIndex": 688 - } - ], - "endIndex": 689, - "startIndex": 687, - "tableCellStyle": { - "backgroundColor": {}, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - }, - { - "content": [ - { - "endIndex": 691, - "paragraph": { - "elements": [ - { - "endIndex": 691, - "startIndex": 690, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": false, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "spacingMode": "COLLAPSE_LISTS" - } - }, - "startIndex": 690 - } - ], - "endIndex": 691, - "startIndex": 689, - "tableCellStyle": { - "backgroundColor": {}, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - }, - { - "content": [ - { - "endIndex": 693, - "paragraph": { - "elements": [ - { - "endIndex": 693, - "startIndex": 692, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": false, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "spacingMode": "COLLAPSE_LISTS" - } - }, - "startIndex": 692 - } - ], - "endIndex": 693, - "startIndex": 691, - "tableCellStyle": { - "backgroundColor": {}, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - }, - { - "content": [ - { - "endIndex": 695, - "paragraph": { - "elements": [ - { - "endIndex": 695, - "startIndex": 694, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": false, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "spacingMode": "COLLAPSE_LISTS" - } - }, - "startIndex": 694 - } - ], - "endIndex": 695, - "startIndex": 693, - "tableCellStyle": { - "backgroundColor": {}, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - }, - { - "content": [ - { - "endIndex": 697, - "paragraph": { - "elements": [ - { - "endIndex": 697, - "startIndex": 696, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": false, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "spacingMode": "COLLAPSE_LISTS" - } - }, - "startIndex": 696 - } - ], - "endIndex": 697, - "startIndex": 695, - "tableCellStyle": { - "backgroundColor": {}, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - }, - { - "content": [ - { - "endIndex": 699, - "paragraph": { - "elements": [ - { - "endIndex": 699, - "startIndex": 698, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": false, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "spacingMode": "COLLAPSE_LISTS" - } - }, - "startIndex": 698 - } - ], - "endIndex": 699, - "startIndex": 697, - "tableCellStyle": { - "backgroundColor": {}, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - } - ], - "tableRowStyle": { - "minRowHeight": { - "unit": "PT" - } - } - } - ], - "tableStyle": { - "tableColumnProperties": [ - { - "widthType": "EVENLY_DISTRIBUTED" - }, - { - "widthType": "EVENLY_DISTRIBUTED" - }, - { - "widthType": "EVENLY_DISTRIBUTED" - }, - { - "widthType": "EVENLY_DISTRIBUTED" - }, - { - "widthType": "EVENLY_DISTRIBUTED" - }, - { - "widthType": "EVENLY_DISTRIBUTED" - } - ] - } - } - }, - { - "endIndex": 701, - "paragraph": { - "elements": [ - { - "endIndex": 701, - "startIndex": 700, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 700 - }, - { - "endIndex": 732, - "paragraph": { - "elements": [ - { - "endIndex": 732, - "startIndex": 701, - "textRun": { - "content": "Special Characters and Symbols\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 701 - }, - { - "endIndex": 831, - "paragraph": { - "elements": [ - { - "endIndex": 831, - "startIndex": 732, - "textRun": { - "content": "This section includes special characters and emoji to test Unicode handling and parsing stability.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 732 - }, - { - "endIndex": 861, - "paragraph": { - "elements": [ - { - "endIndex": 861, - "startIndex": 831, - "textRun": { - "content": "Emoji test: \ud83d\ude00 \ud83d\ude0e \ud83d\ude80 \ud83c\udf0d \ud83d\udca1 \ud83d\udd25\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 831 - }, - { - "endIndex": 917, - "paragraph": { - "elements": [ - { - "endIndex": 917, - "startIndex": 861, - "textRun": { - "content": "Symbols test: \u00a9 \u00ae \u2122 \u2211 \u221a \u221e \u03a9 \u2248 \u2264 \u2265 \u00b1 \u00f7 \u00d7 \u00b5 \u00a7 \u00b6 \u2022 \u00a2 \u00a3 \u00a5 \u20ac\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 861 - }, - { - "endIndex": 999, - "paragraph": { - "elements": [ - { - "endIndex": 999, - "startIndex": 917, - "textRun": { - "content": "Combined diacritics: a\u0301 e\u0302 i\u0308 o\u0303 u\u0304 \u2014 ensure these remain distinct when imported.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 917 - }, - { - "endIndex": 1025, - "paragraph": { - "elements": [ - { - "endIndex": 1025, - "startIndex": 999, - "textRun": { - "content": "Oversized Content Example\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 999 - }, - { - "endIndex": 1232, - "paragraph": { - "elements": [ - { - "endIndex": 1232, - "startIndex": 1025, - "textRun": { - "content": "This section simulates a document exceeding expected content limits. In production, this would include extremely long text blocks or deeply nested JSON-like content to trigger import size handling. Example:\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 1025 - }, - { - "endIndex": 6249, - "paragraph": { - "elements": [ - { - "endIndex": 6249, - "startIndex": 1232, - "textRun": { - "content": "{ 'sample': 'data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data data ' }\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 1232 - }, - { - "endIndex": 6266, - "paragraph": { - "elements": [ - { - "endIndex": 6266, - "startIndex": 6249, - "textRun": { - "content": "Expected Outcome\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 6249 - }, - { - "endIndex": 6470, - "paragraph": { - "elements": [ - { - "endIndex": 6470, - "startIndex": 6266, - "textRun": { - "content": "Importer should successfully process malformed and special content without throwing unhandled exceptions. Empty or oversized documents should be logged and skipped gracefully, preserving diagnostic data.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 6266 - } - ] - }, - "documentStyle": { - "background": { - "color": {} - }, - "documentFormat": { - "documentMode": "PAGES" - }, - "marginBottom": { - "magnitude": 72, - "unit": "PT" - }, - "marginFooter": { - "magnitude": 36, - "unit": "PT" - }, - "marginHeader": { - "magnitude": 36, - "unit": "PT" - }, - "marginLeft": { - "magnitude": 90, - "unit": "PT" - }, - "marginRight": { - "magnitude": 90, - "unit": "PT" - }, - "marginTop": { - "magnitude": 72, - "unit": "PT" - }, - "pageNumberStart": 1, - "pageSize": { - "height": { - "magnitude": 792, - "unit": "PT" - }, - "width": { - "magnitude": 612, - "unit": "PT" - } - }, - "useCustomHeaderFooterMargins": true - }, - "lists": { - "kix.list.1": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Noto Sans Symbols", - "weight": 400 - } - } - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.2": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Noto Sans Symbols", - "weight": 400 - } - } - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.3": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 54, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Noto Sans Symbols", - "weight": 400 - } - } - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.4": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Noto Sans Symbols", - "weight": 400 - } - } - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.5": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "magnitude": 18, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.6": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.7": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "magnitude": 36, - "unit": "PT" - }, - "indentStart": { - "magnitude": 54, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.8": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - }, - "kix.list.9": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "magnitude": 72, - "unit": "PT" - }, - "indentStart": { - "magnitude": 90, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - }, - { - "bulletAlignment": "START", - "glyphType": "NONE", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "startNumber": 1, - "textStyle": {} - } - ] - } - } - }, - "namedStyles": { - "styles": [ - { - "namedStyleType": "NORMAL_TEXT", - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": true, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 115, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 10, - "unit": "PT" - }, - "spacingMode": "NEVER_COLLAPSE" - }, - "textStyle": { - "backgroundColor": {}, - "baselineOffset": "NONE", - "bold": false, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "italic": false, - "smallCaps": false, - "strikethrough": false, - "underline": false, - "weightedFontFamily": { - "fontFamily": "Cambria", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_1", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 24, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "bold": true, - "fontSize": { - "magnitude": 14, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.5686275, - "green": 0.3764706, - "red": 0.21176471 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_2", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "bold": true, - "fontSize": { - "magnitude": 13, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_3", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "bold": true, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_4", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "bold": true, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "italic": true, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_5", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.38039216, - "green": 0.24705882, - "red": 0.14117648 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_6", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 10, - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - } - }, - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.38039216, - "green": 0.24705882, - "red": 0.14117648 - } - } - }, - "italic": true, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "TITLE", - "paragraphStyle": { - "borderBottom": { - "color": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "dashStyle": "SOLID", - "padding": { - "magnitude": 4, - "unit": "PT" - }, - "width": { - "magnitude": 1, - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT", - "spaceBelow": { - "magnitude": 15, - "unit": "PT" - }, - "spacingMode": "NEVER_COLLAPSE" - }, - "textStyle": { - "fontSize": { - "magnitude": 26, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.3647059, - "green": 0.21176471, - "red": 0.09019608 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - }, - { - "namedStyleType": "SUBTITLE", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - }, - "textStyle": { - "fontSize": { - "magnitude": 12, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.7411765, - "green": 0.5058824, - "red": 0.30980393 - } - } - }, - "italic": true, - "weightedFontFamily": { - "fontFamily": "Calibri", - "weight": 400 - } - } - } - ] - } - }, - "tabProperties": { - "index": 0, - "tabId": "t.0", - "title": "Tab 1" - } - } - ], - "title": "Doc_7_Edge_Cases_Test" -} \ No newline at end of file diff --git a/apps/google-docs/src/utils/test_docs_json/Doc_8_DXP_benefits - Sample.json b/apps/google-docs/src/utils/test_docs_json/Doc_8_DXP_benefits - Sample.json deleted file mode 100644 index c40c90d553..0000000000 --- a/apps/google-docs/src/utils/test_docs_json/Doc_8_DXP_benefits - Sample.json +++ /dev/null @@ -1,7791 +0,0 @@ -{ - "documentId": "1JixZUQYGXe7ajWXdod-mweSJgFaEu-sC3so8O-fPmJM", - "suggestionsViewMode": "PREVIEW_WITHOUT_SUGGESTIONS", - "tabs": [ - { - "documentTab": { - "body": { - "content": [ - { - "endIndex": 1, - "sectionBreak": { - "sectionStyle": { - "columnSeparatorStyle": "NONE", - "contentDirection": "LEFT_TO_RIGHT", - "sectionType": "CONTINUOUS" - } - } - }, - { - "endIndex": 2, - "paragraph": { - "elements": [ - { - "endIndex": 2, - "startIndex": 1, - "textRun": { - "content": "\n", - "textStyle": { - "backgroundColor": { - "color": { - "rgbColor": { - "green": 1 - } - } - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT", - "spacingMode": "NEVER_COLLAPSE" - } - }, - "startIndex": 1 - }, - { - "endIndex": 493, - "startIndex": 2, - "table": { - "columns": 2, - "rows": 6, - "tableRows": [ - { - "endIndex": 32, - "startIndex": 3, - "tableCells": [ - { - "content": [ - { - "endIndex": 8, - "paragraph": { - "elements": [ - { - "endIndex": 8, - "startIndex": 5, - "textRun": { - "content": "KW\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "lineSpacing": 120.00001, - "namedStyleType": "NORMAL_TEXT", - "spacingMode": "NEVER_COLLAPSE" - } - }, - "startIndex": 5 - }, - { - "endIndex": 9, - "paragraph": { - "elements": [ - { - "endIndex": 9, - "startIndex": 8, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT", - "spacingMode": "NEVER_COLLAPSE" - } - }, - "startIndex": 8 - } - ], - "endIndex": 9, - "startIndex": 4, - "tableCellStyle": { - "borderBottom": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderLeft": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderRight": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderTop": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - }, - { - "content": [ - { - "endIndex": 31, - "paragraph": { - "elements": [ - { - "endIndex": 31, - "startIndex": 10, - "textRun": { - "content": "benefits of dxp (10)\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "alignment": "CENTER", - "avoidWidowAndOrphan": false, - "direction": "LEFT_TO_RIGHT", - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 10 - }, - { - "endIndex": 32, - "paragraph": { - "elements": [ - { - "endIndex": 32, - "startIndex": 31, - "textRun": { - "content": "\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "alignment": "CENTER", - "avoidWidowAndOrphan": false, - "direction": "LEFT_TO_RIGHT", - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 31 - } - ], - "endIndex": 32, - "startIndex": 9, - "tableCellStyle": { - "borderBottom": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderLeft": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderRight": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderTop": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - } - ], - "tableRowStyle": { - "minRowHeight": { - "magnitude": 63.75, - "unit": "PT" - } - } - }, - { - "endIndex": 223, - "startIndex": 32, - "tableCells": [ - { - "content": [ - { - "endIndex": 57, - "paragraph": { - "elements": [ - { - "endIndex": 57, - "startIndex": 34, - "textRun": { - "content": "Meta description (new)\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "lineSpacing": 120.00001, - "namedStyleType": "NORMAL_TEXT", - "spacingMode": "NEVER_COLLAPSE" - } - }, - "startIndex": 34 - } - ], - "endIndex": 57, - "startIndex": 33, - "tableCellStyle": { - "borderBottom": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderLeft": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderRight": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderTop": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - }, - { - "content": [ - { - "endIndex": 222, - "paragraph": { - "elements": [ - { - "endIndex": 222, - "startIndex": 58, - "textRun": { - "content": "Learn what we think the benefits of a DXP should be, why some DXPs fall short, and how to pick a platform to deliver the digital experiences that customers expect.\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 58 - }, - { - "endIndex": 223, - "paragraph": { - "elements": [ - { - "endIndex": 223, - "startIndex": 222, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "lineSpacing": 120.00001, - "namedStyleType": "NORMAL_TEXT", - "spacingMode": "NEVER_COLLAPSE" - } - }, - "startIndex": 222 - } - ], - "endIndex": 223, - "startIndex": 57, - "tableCellStyle": { - "borderBottom": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderLeft": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderRight": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderTop": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - } - ], - "tableRowStyle": { - "minRowHeight": { - "magnitude": 50.25, - "unit": "PT" - } - } - }, - { - "endIndex": 235, - "startIndex": 223, - "tableCells": [ - { - "content": [ - { - "endIndex": 230, - "paragraph": { - "elements": [ - { - "endIndex": 230, - "startIndex": 225, - "textRun": { - "content": "Slug\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "lineSpacing": 120.00001, - "namedStyleType": "NORMAL_TEXT", - "spacingMode": "NEVER_COLLAPSE" - } - }, - "startIndex": 225 - } - ], - "endIndex": 230, - "startIndex": 224, - "tableCellStyle": { - "borderBottom": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderLeft": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderRight": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderTop": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - }, - { - "content": [ - { - "endIndex": 235, - "paragraph": { - "elements": [ - { - "endIndex": 234, - "startIndex": 231, - "textRun": { - "content": "TBD", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 235, - "startIndex": 234, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "avoidWidowAndOrphan": false, - "direction": "LEFT_TO_RIGHT", - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 231 - } - ], - "endIndex": 235, - "startIndex": 230, - "tableCellStyle": { - "borderBottom": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderLeft": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderRight": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderTop": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - } - ], - "tableRowStyle": { - "minRowHeight": { - "magnitude": 37.5, - "unit": "PT" - } - } - }, - { - "endIndex": 388, - "startIndex": 235, - "tableCells": [ - { - "content": [ - { - "endIndex": 250, - "paragraph": { - "elements": [ - { - "endIndex": 250, - "startIndex": 237, - "textRun": { - "content": "Header image\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "lineSpacing": 120.00001, - "namedStyleType": "NORMAL_TEXT", - "spacingMode": "NEVER_COLLAPSE" - } - }, - "startIndex": 237 - } - ], - "endIndex": 250, - "startIndex": 236, - "tableCellStyle": { - "borderBottom": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderLeft": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderRight": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderTop": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - }, - { - "content": [ - { - "endIndex": 253, - "paragraph": { - "elements": [ - { - "endIndex": 252, - "inlineObjectElement": { - "inlineObjectId": "kix.nbcfdc81186i", - "textStyle": {} - }, - "startIndex": 251 - }, - { - "endIndex": 253, - "startIndex": 252, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "lineSpacing": 120.00001, - "namedStyleType": "NORMAL_TEXT", - "spacingMode": "NEVER_COLLAPSE" - } - }, - "startIndex": 251 - }, - { - "endIndex": 254, - "paragraph": { - "elements": [ - { - "endIndex": 254, - "startIndex": 253, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "lineSpacing": 120.00001, - "namedStyleType": "NORMAL_TEXT", - "spacingMode": "NEVER_COLLAPSE" - } - }, - "startIndex": 253 - }, - { - "endIndex": 388, - "paragraph": { - "elements": [ - { - "endIndex": 388, - "startIndex": 254, - "textRun": { - "content": "https://www.figma.com/design/gMCLiAW4j4oacoC0l6czCl/Contentful---Article-illustrations---Design?node-id=1749-929&t=NjCfvWzE17RHoCJy-0\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "lineSpacing": 120.00001, - "namedStyleType": "NORMAL_TEXT", - "spacingMode": "NEVER_COLLAPSE" - } - }, - "startIndex": 254 - } - ], - "endIndex": 388, - "startIndex": 250, - "tableCellStyle": { - "borderBottom": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderLeft": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderRight": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderTop": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - } - ], - "tableRowStyle": { - "minRowHeight": { - "magnitude": 24, - "unit": "PT" - } - } - }, - { - "endIndex": 409, - "startIndex": 388, - "tableCells": [ - { - "content": [ - { - "endIndex": 394, - "paragraph": { - "elements": [ - { - "endIndex": 394, - "startIndex": 390, - "textRun": { - "content": "SME\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "lineSpacing": 120.00001, - "namedStyleType": "NORMAL_TEXT", - "spacingMode": "NEVER_COLLAPSE" - } - }, - "startIndex": 390 - } - ], - "endIndex": 394, - "startIndex": 389, - "tableCellStyle": { - "borderBottom": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderLeft": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderRight": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderTop": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - }, - { - "content": [ - { - "endIndex": 409, - "paragraph": { - "elements": [ - { - "endIndex": 409, - "startIndex": 395, - "textRun": { - "content": "Taylor Wagner\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "lineSpacing": 120.00001, - "namedStyleType": "NORMAL_TEXT", - "spacingMode": "NEVER_COLLAPSE" - } - }, - "startIndex": 395 - } - ], - "endIndex": 409, - "startIndex": 394, - "tableCellStyle": { - "borderBottom": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderLeft": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderRight": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderTop": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - } - ], - "tableRowStyle": { - "minRowHeight": { - "magnitude": 24, - "unit": "PT" - } - } - }, - { - "endIndex": 492, - "startIndex": 409, - "tableCells": [ - { - "content": [ - { - "endIndex": 423, - "paragraph": { - "elements": [ - { - "endIndex": 423, - "startIndex": 411, - "textRun": { - "content": "Surfer page\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "lineSpacing": 120.00001, - "namedStyleType": "NORMAL_TEXT", - "spacingMode": "NEVER_COLLAPSE" - } - }, - "startIndex": 411 - } - ], - "endIndex": 423, - "startIndex": 410, - "tableCellStyle": { - "borderBottom": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderLeft": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderRight": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderTop": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - }, - { - "content": [ - { - "endIndex": 492, - "paragraph": { - "elements": [ - { - "endIndex": 491, - "startIndex": 424, - "textRun": { - "content": "https://app.surferseo.com/drafts/s/NpNIHs1elgXUosNwUYYxjHaOCxTUQbO4", - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.8, - "green": 0.33333334, - "red": 0.06666667 - } - } - }, - "link": { - "url": "https://app.surferseo.com/drafts/s/NpNIHs1elgXUosNwUYYxjHaOCxTUQbO4" - }, - "underline": true, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 492, - "startIndex": 491, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "avoidWidowAndOrphan": false, - "direction": "LEFT_TO_RIGHT", - "lineSpacing": 100, - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 424 - } - ], - "endIndex": 492, - "startIndex": 423, - "tableCellStyle": { - "borderBottom": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderLeft": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderRight": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "borderTop": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "width": { - "magnitude": 0.75, - "unit": "PT" - } - }, - "columnSpan": 1, - "contentAlignment": "TOP", - "paddingBottom": { - "magnitude": 5, - "unit": "PT" - }, - "paddingLeft": { - "magnitude": 5, - "unit": "PT" - }, - "paddingRight": { - "magnitude": 5, - "unit": "PT" - }, - "paddingTop": { - "magnitude": 5, - "unit": "PT" - }, - "rowSpan": 1 - } - } - ], - "tableRowStyle": { - "minRowHeight": { - "magnitude": 24, - "unit": "PT" - } - } - } - ], - "tableStyle": { - "tableColumnProperties": [ - { - "width": { - "magnitude": 86.25, - "unit": "PT" - }, - "widthType": "FIXED_WIDTH" - }, - { - "width": { - "magnitude": 357, - "unit": "PT" - }, - "widthType": "FIXED_WIDTH" - } - ] - } - } - }, - { - "endIndex": 510, - "paragraph": { - "elements": [ - { - "endIndex": 509, - "startIndex": 493, - "textRun": { - "content": "Draft - approved", - "textStyle": { - "bold": true, - "fontSize": { - "magnitude": 15, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 1 - } - } - } - } - } - }, - { - "endIndex": 510, - "startIndex": 509, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 24, - "unit": "PT" - } - } - }, - "startIndex": 493 - }, - { - "endIndex": 511, - "paragraph": { - "elements": [ - { - "endIndex": 511, - "startIndex": 510, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 510 - }, - { - "endIndex": 527, - "paragraph": { - "elements": [ - { - "endIndex": 527, - "startIndex": 511, - "textRun": { - "content": "Title options: \n", - "textStyle": { - "bold": true, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 24, - "unit": "PT" - } - } - }, - "startIndex": 511 - }, - { - "endIndex": 589, - "paragraph": { - "bullet": { - "listId": "kix.vdrltauyvi44", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - }, - "elements": [ - { - "endIndex": 589, - "startIndex": 527, - "textRun": { - "content": "Want the full benefits of a DXP? Choose a composable platform\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 24, - "unit": "PT" - } - } - }, - "startIndex": 527 - }, - { - "endIndex": 625, - "paragraph": { - "bullet": { - "listId": "kix.vdrltauyvi44", - "textStyle": { - "underline": false - } - }, - "elements": [ - { - "endIndex": 625, - "startIndex": 589, - "textRun": { - "content": "The real benefits of adopting a DXP\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 24, - "unit": "PT" - } - } - }, - "startIndex": 589 - }, - { - "endIndex": 683, - "paragraph": { - "bullet": { - "listId": "kix.vdrltauyvi44", - "textStyle": { - "backgroundColor": { - "color": { - "rgbColor": { - "green": 1, - "red": 1 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - }, - "elements": [ - { - "endIndex": 682, - "startIndex": 625, - "textRun": { - "content": "Composability is the key to reaping the benefits of a DXP", - "textStyle": { - "backgroundColor": { - "color": { - "rgbColor": { - "green": 1, - "red": 1 - } - } - }, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 683, - "startIndex": 682, - "textRun": { - "content": "\n", - "textStyle": { - "backgroundColor": { - "color": { - "rgbColor": { - "green": 1, - "red": 1 - } - } - }, - "foregroundColor": { - "color": { - "rgbColor": { - "red": 1 - } - } - }, - "italic": true, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 24, - "unit": "PT" - } - } - }, - "startIndex": 625 - }, - { - "endIndex": 684, - "paragraph": { - "elements": [ - { - "endIndex": 684, - "startIndex": 683, - "textRun": { - "content": "\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 683 - }, - { - "endIndex": 691, - "paragraph": { - "elements": [ - { - "endIndex": 690, - "startIndex": 684, - "textRun": { - "content": "Intro:", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 691, - "startIndex": 690, - "textRun": { - "content": "\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "headingId": "h.kbkjud7wkhui", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 684 - }, - { - "endIndex": 1037, - "paragraph": { - "elements": [ - { - "endIndex": 1037, - "startIndex": 691, - "textRun": { - "content": "Imagine going through an RFP process, picking a vendor, and spending a year implementing a new digital experience platform (DXP), only to find out that you\u2019re still unable to do everything you want. This is the nightmare some marketing leaders face. They\u2019ve invested time and money in a new platform but have failed to get the benefits of a DXP.\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 691 - }, - { - "endIndex": 1038, - "paragraph": { - "elements": [ - { - "endIndex": 1038, - "startIndex": 1037, - "textRun": { - "content": "\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 1037 - }, - { - "endIndex": 1107, - "paragraph": { - "elements": [ - { - "endIndex": 1107, - "startIndex": 1038, - "textRun": { - "content": "So, what went wrong, and how can you avoid making a similar mistake?\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 1038 - }, - { - "endIndex": 1108, - "paragraph": { - "elements": [ - { - "endIndex": 1108, - "startIndex": 1107, - "textRun": { - "content": "\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 1107 - }, - { - "endIndex": 1324, - "paragraph": { - "elements": [ - { - "endIndex": 1324, - "startIndex": 1108, - "textRun": { - "content": "In this post, you\u2019ll learn what we think the benefits of a DXP should be, why some DXPs fall short, and how to pick a platform that will empower your business to create the digital experiences that customers expect.\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 1108 - }, - { - "endIndex": 1365, - "paragraph": { - "elements": [ - { - "endIndex": 1365, - "startIndex": 1324, - "textRun": { - "content": "What benefits can you expect from a DXP?\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "headingId": "h.w2lv6ljnowy6", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 1324 - }, - { - "endIndex": 1633, - "paragraph": { - "elements": [ - { - "endIndex": 1393, - "startIndex": 1365, - "textRun": { - "content": "Most analysts and companies ", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 1405, - "startIndex": 1393, - "textRun": { - "content": "define a DXP", - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.8, - "green": 0.33333334, - "red": 0.06666667 - } - } - }, - "link": { - "url": "https://www.contentful.com/blog/whats-a-digital-experience-platform/" - }, - "underline": true, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 1633, - "startIndex": 1405, - "textRun": { - "content": " as a set of technologies that lets you create, manage, and deliver customer experiences across digital channels. That sounds simple enough, but it takes multiple capabilities to produce truly seamless, omnichannel experiences.\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 1365 - }, - { - "endIndex": 1634, - "paragraph": { - "elements": [ - { - "endIndex": 1634, - "startIndex": 1633, - "textRun": { - "content": "\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 1633 - }, - { - "endIndex": 1703, - "paragraph": { - "elements": [ - { - "endIndex": 1703, - "startIndex": 1634, - "textRun": { - "content": "At Contentful, we believe the core benefits of a DXP should include:\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 1634 - }, - { - "endIndex": 1704, - "paragraph": { - "elements": [ - { - "endIndex": 1704, - "startIndex": 1703, - "textRun": { - "content": "\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 1703 - }, - { - "endIndex": 1878, - "paragraph": { - "bullet": { - "listId": "kix.8x8klvcu13zf", - "textStyle": {} - }, - "elements": [ - { - "endIndex": 1721, - "startIndex": 1704, - "textRun": { - "content": "Brand consistency", - "textStyle": { - "bold": true, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 1817, - "startIndex": 1721, - "textRun": { - "content": " through unified digital content management and modular, reusable content that enables teams to ", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 1836, - "startIndex": 1817, - "textRun": { - "content": "assemble consistent", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 1878, - "startIndex": 1836, - "textRun": { - "content": " experiences across regions and channels.\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 1704 - }, - { - "endIndex": 1993, - "paragraph": { - "bullet": { - "listId": "kix.8x8klvcu13zf", - "textStyle": {} - }, - "elements": [ - { - "endIndex": 1898, - "startIndex": 1878, - "textRun": { - "content": "Omnichannel delivery", - "textStyle": { - "bold": true, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 1993, - "startIndex": 1898, - "textRun": { - "content": " to meet your customers where they are with experiences tailored for every digital touchpoint.\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 1878 - }, - { - "endIndex": 2117, - "paragraph": { - "bullet": { - "listId": "kix.8x8klvcu13zf", - "textStyle": {} - }, - "elements": [ - { - "endIndex": 2014, - "startIndex": 1993, - "textRun": { - "content": "Integrated operations", - "textStyle": { - "bold": true, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 2117, - "startIndex": 2014, - "textRun": { - "content": " that eliminate silos for seamless collaboration that empowers teams to assemble cohesive experiences.\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 1993 - }, - { - "endIndex": 2327, - "paragraph": { - "bullet": { - "listId": "kix.8x8klvcu13zf", - "textStyle": {} - }, - "elements": [ - { - "endIndex": 2144, - "startIndex": 2117, - "textRun": { - "content": "Data-driven personalization", - "textStyle": { - "bold": true, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 2327, - "startIndex": 2144, - "textRun": { - "content": " that drives measurable return on investment (ROI) by enabling teams to experiment, gain valuable insights from customer data, and use those insights to optimize digital experiences.\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 2117 - }, - { - "endIndex": 2398, - "paragraph": { - "bullet": { - "listId": "kix.8x8klvcu13zf", - "textStyle": {} - }, - "elements": [ - { - "endIndex": 2344, - "startIndex": 2327, - "textRun": { - "content": "Speed and agility", - "textStyle": { - "bold": true, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 2398, - "startIndex": 2344, - "textRun": { - "content": " so you can ship faster and act on data in real time.\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 2327 - }, - { - "endIndex": 2486, - "paragraph": { - "bullet": { - "listId": "kix.8x8klvcu13zf", - "textStyle": {} - }, - "elements": [ - { - "endIndex": 2420, - "startIndex": 2398, - "textRun": { - "content": "Increased productivity", - "textStyle": { - "bold": true, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 2486, - "startIndex": 2420, - "textRun": { - "content": " through modular content, automated workflows, and contextual AI.\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 2398 - }, - { - "endIndex": 2607, - "paragraph": { - "bullet": { - "listId": "kix.8x8klvcu13zf", - "textStyle": {} - }, - "elements": [ - { - "endIndex": 2526, - "startIndex": 2486, - "textRun": { - "content": "Enterprise-level security and governance", - "textStyle": { - "bold": true, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 2606, - "startIndex": 2526, - "textRun": { - "content": ", including secure frameworks, reliable infrastructure, and compliance at scale.", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 2607, - "startIndex": 2606, - "textRun": { - "content": "\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 2486 - }, - { - "endIndex": 2608, - "paragraph": { - "elements": [ - { - "endIndex": 2608, - "startIndex": 2607, - "textRun": { - "content": "\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 2607 - }, - { - "endIndex": 2682, - "paragraph": { - "elements": [ - { - "endIndex": 2681, - "startIndex": 2608, - "textRun": { - "content": "But not all DXPs provide these benefits to the extent modern brands need.", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 2682, - "startIndex": 2681, - "textRun": { - "content": "\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 2608 - }, - { - "endIndex": 2749, - "paragraph": { - "elements": [ - { - "endIndex": 2749, - "startIndex": 2682, - "textRun": { - "content": "Monolithic vs. composable: Why the type of DXP you choose matters \n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "headingId": "h.52jp1v9u9uog", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 2682 - }, - { - "endIndex": 2895, - "paragraph": { - "elements": [ - { - "endIndex": 2895, - "startIndex": 2749, - "textRun": { - "content": "Because the definition of a DXP is so broad, the benefits of a DXP depend on the kind of DXP you choose, the DXP vendor, and the implementation. \n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 2749 - }, - { - "endIndex": 2896, - "paragraph": { - "elements": [ - { - "endIndex": 2896, - "startIndex": 2895, - "textRun": { - "content": "\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 2895 - }, - { - "endIndex": 3009, - "paragraph": { - "elements": [ - { - "endIndex": 3009, - "startIndex": 2896, - "textRun": { - "content": "There are two main types of DXP: traditional or monolithic, and composable. Each type offers different benefits.\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 2896 - }, - { - "endIndex": 3026, - "paragraph": { - "elements": [ - { - "endIndex": 3026, - "startIndex": 3009, - "textRun": { - "content": "Monolithic DXPs \n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "headingId": "h.8r9w6vhs9j0e", - "namedStyleType": "HEADING_3" - } - }, - "startIndex": 3009 - }, - { - "endIndex": 3197, - "paragraph": { - "elements": [ - { - "endIndex": 3197, - "startIndex": 3026, - "textRun": { - "content": "Monolithic or traditional DXPs offer the convenience of an all-in-one solution or suite of tools. However, this convenience comes at the cost of flexibility and control. \n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 3026 - }, - { - "endIndex": 3198, - "paragraph": { - "elements": [ - { - "endIndex": 3198, - "startIndex": 3197, - "textRun": { - "content": "\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 3197 - }, - { - "endIndex": 3477, - "paragraph": { - "elements": [ - { - "endIndex": 3324, - "startIndex": 3198, - "textRun": { - "content": "Fewer choices might initially be less overwhelming, but limiting yourself to a single vendor can leave you experiencing vendor", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 3332, - "startIndex": 3324, - "textRun": { - "content": " lock-in", - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.8, - "green": 0.33333334, - "red": 0.06666667 - } - } - }, - "link": { - "url": "https://www.contentful.com/blog/vendor-lock-in/" - }, - "underline": true, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 3334, - "startIndex": 3332, - "textRun": { - "content": ". ", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 3477, - "startIndex": 3334, - "textRun": { - "content": "Often, this is associated with paying for features you don\u2019t use and encountering unexpected limitations that necessitate costly integrations.\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 3198 - }, - { - "endIndex": 3478, - "paragraph": { - "elements": [ - { - "endIndex": 3478, - "startIndex": 3477, - "textRun": { - "content": "\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 3477 - }, - { - "endIndex": 3654, - "paragraph": { - "elements": [ - { - "endIndex": 3653, - "startIndex": 3478, - "textRun": { - "content": "The inability to right-size your tech stack and the frustration of integrating more tools into something that promised all-in-one bliss negates the benefit of monolithic DXPs.", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 3654, - "startIndex": 3653, - "textRun": { - "content": "\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 3478 - }, - { - "endIndex": 3670, - "paragraph": { - "elements": [ - { - "endIndex": 3670, - "startIndex": 3654, - "textRun": { - "content": "Composable DXPs\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "headingId": "h.9ywz3533b8rm", - "namedStyleType": "HEADING_3" - } - }, - "startIndex": 3654 - }, - { - "endIndex": 3930, - "paragraph": { - "elements": [ - { - "endIndex": 3888, - "startIndex": 3670, - "textRun": { - "content": "In contrast, composable DXPs, like Contentful, acknowledge that no single vendor can provide the best tools for everything you need. Instead, we focus on providing a core set of capabilities and making it ridiculously", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 3927, - "startIndex": 3888, - "textRun": { - "content": " easy to integrate your preferred tools", - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.8, - "green": 0.33333334, - "red": 0.06666667 - } - } - }, - "link": { - "url": "https://www.contentful.com/products/ecosystem/" - }, - "underline": true, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 3930, - "startIndex": 3927, - "textRun": { - "content": ". \n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 3670 - }, - { - "endIndex": 3931, - "paragraph": { - "elements": [ - { - "endIndex": 3931, - "startIndex": 3930, - "textRun": { - "content": "\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 3930 - }, - { - "endIndex": 4220, - "paragraph": { - "elements": [ - { - "endIndex": 3942, - "startIndex": 3931, - "textRun": { - "content": "Adopting a ", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 3965, - "startIndex": 3942, - "textRun": { - "content": "composable architecture", - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.8, - "green": 0.33333334, - "red": 0.06666667 - } - } - }, - "link": { - "url": "https://www.contentful.com/composable-content/architecture/" - }, - "underline": true, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 4012, - "startIndex": 3965, - "textRun": { - "content": " allows modern DXPs to offer the benefit of an ", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 4042, - "startIndex": 4012, - "textRun": { - "content": "infinitely extensible platform", - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.8, - "green": 0.33333334, - "red": 0.06666667 - } - } - }, - "link": { - "url": "https://www.contentful.com/products/platform/" - }, - "underline": true, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 4220, - "startIndex": 4042, - "textRun": { - "content": " that meets you where you are now and supports your future needs. It\u2019s like assembling a dream team where you have access to any player you want and can trade them at any time. \n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 3931 - }, - { - "endIndex": 4221, - "paragraph": { - "elements": [ - { - "endIndex": 4221, - "startIndex": 4220, - "textRun": { - "content": "\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 4220 - }, - { - "endIndex": 4302, - "paragraph": { - "elements": [ - { - "endIndex": 4302, - "startIndex": 4221, - "textRun": { - "content": "In addition to the core benefits of a DXP, a composable DXP also enables you to:\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 4221 - }, - { - "endIndex": 4303, - "paragraph": { - "elements": [ - { - "endIndex": 4303, - "startIndex": 4302, - "textRun": { - "content": "\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 4302 - }, - { - "endIndex": 4478, - "paragraph": { - "bullet": { - "listId": "kix.xn14b7z3imo2", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - }, - "elements": [ - { - "endIndex": 4338, - "startIndex": 4303, - "textRun": { - "content": "Maximize existing tech investments.", - "textStyle": { - "bold": true, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 4478, - "startIndex": 4338, - "textRun": { - "content": " Instead of forcing you to give up tools you love, a composable approach lets you combine existing tech with a modern DXP to maximize ROI. \n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 4303 - }, - { - "endIndex": 4756, - "paragraph": { - "bullet": { - "listId": "kix.xn14b7z3imo2", - "textStyle": { - "bold": true, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - }, - "elements": [ - { - "endIndex": 4501, - "startIndex": 4478, - "textRun": { - "content": "Reduce technical debt. ", - "textStyle": { - "bold": true, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 4590, - "startIndex": 4501, - "textRun": { - "content": "Assemble the stack you need from the start without the burden of features you won\u2019t use. ", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 4623, - "startIndex": 4590, - "textRun": { - "content": "Everything is modular, so you can", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 4756, - "startIndex": 4623, - "textRun": { - "content": " change and update tools without the fear of breaking things. No more missing page elements or broken tracking tags after an update.\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 4478 - }, - { - "endIndex": 4953, - "paragraph": { - "bullet": { - "listId": "kix.xn14b7z3imo2", - "textStyle": { - "underline": false, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - }, - "elements": [ - { - "endIndex": 4781, - "startIndex": 4756, - "textRun": { - "content": "Choose the best vendors. ", - "textStyle": { - "bold": true, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 4953, - "startIndex": 4781, - "textRun": { - "content": "A composable DXP that integrates tools from multiple vendors into one connected system gives you what you want for each function without the typical integration headaches.\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 4756 - }, - { - "endIndex": 5235, - "paragraph": { - "bullet": { - "listId": "kix.xn14b7z3imo2", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - }, - "elements": [ - { - "endIndex": 4982, - "startIndex": 4953, - "textRun": { - "content": "Create and connect new apps. ", - "textStyle": { - "bold": true, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 5130, - "startIndex": 4982, - "textRun": { - "content": "The Contentful Marketplace has 120+ pre-built integrations, making it easy to get the new features you need. What\u2019s more, you can use Contentful\u2019s ", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 5144, - "startIndex": 5130, - "textRun": { - "content": "App Framework ", - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.8, - "green": 0.33333334, - "red": 0.06666667 - } - } - }, - "link": { - "url": "https://www.contentful.com/developers/docs/extensibility/app-framework/" - }, - "underline": true, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 5235, - "startIndex": 5144, - "textRun": { - "content": "to build exactly what you want, turning your platform into a unique competitive advantage.\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 4953 - }, - { - "endIndex": 5447, - "paragraph": { - "bullet": { - "listId": "kix.xn14b7z3imo2", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - }, - "elements": [ - { - "endIndex": 5259, - "startIndex": 5235, - "textRun": { - "content": "Be ready for the future.", - "textStyle": { - "bold": true, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 5447, - "startIndex": 5259, - "textRun": { - "content": " AI has already changed the way businesses create and ship digital experiences. Who knows what will be next? But with a composable platform, you\u2019ll be ready to plug it in and try it out. \n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 5235 - }, - { - "endIndex": 5499, - "paragraph": { - "elements": [ - { - "endIndex": 5499, - "startIndex": 5447, - "textRun": { - "content": "Contentful makes getting the benefits of a DXP easy\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "headingId": "h.7kxzmcz3z54r", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 5447 - }, - { - "endIndex": 5743, - "paragraph": { - "elements": [ - { - "endIndex": 5608, - "startIndex": 5499, - "textRun": { - "content": "A composable approach might sound like too many options, but the right partners make it easy to get started. ", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 5640, - "startIndex": 5608, - "textRun": { - "content": "Contentful Professional Services", - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.8, - "green": 0.33333334, - "red": 0.06666667 - } - } - }, - "link": { - "url": "https://www.contentful.com/services/professional-services/" - }, - "underline": true, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 5649, - "startIndex": 5640, - "textRun": { - "content": " and our ", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 5674, - "startIndex": 5649, - "textRun": { - "content": "extensive partner network", - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.8, - "green": 0.33333334, - "red": 0.06666667 - } - } - }, - "link": { - "url": "https://www.contentful.com/partners/" - }, - "underline": true, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 5743, - "startIndex": 5674, - "textRun": { - "content": " offer expert guidance to help you get the benefits of a DXP faster.\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 5499 - }, - { - "endIndex": 5820, - "paragraph": { - "elements": [ - { - "endIndex": 5820, - "startIndex": 5743, - "textRun": { - "content": "BMW uses Contentful to power digital experiences across 160 dealership sites\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "headingId": "h.dco7thowqe5c", - "namedStyleType": "HEADING_3" - } - }, - "startIndex": 5743 - }, - { - "endIndex": 6263, - "paragraph": { - "elements": [ - { - "endIndex": 6133, - "startIndex": 5820, - "textRun": { - "content": "BMW is a great example of how the right partner can fast-track results. Faced with the rise of online shopping, BMW set out to set a new standard in digital experiences. First, they needed a platform to connect a tangle of backend systems and content management systems (CMSes). In partnership with design agency ", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 6137, - "startIndex": 6133, - "textRun": { - "content": "TMWX", - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.8, - "green": 0.33333334, - "red": 0.06666667 - } - } - }, - "link": { - "url": "https://www.contentful.com/partners/solutions/tmwx/" - }, - "underline": true, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 6263, - "startIndex": 6137, - "textRun": { - "content": " (now part of Accenture Song), they chose Contentful to modernize the digital experience across 160 dealer-specific websites.\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 5820 - }, - { - "endIndex": 6264, - "paragraph": { - "elements": [ - { - "endIndex": 6264, - "startIndex": 6263, - "textRun": { - "content": "\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 6263 - }, - { - "endIndex": 6540, - "paragraph": { - "elements": [ - { - "endIndex": 6358, - "startIndex": 6264, - "textRun": { - "content": "TMWX designed a digital experience that feels like an extension of the retailer. Contentful\u2019s ", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 6373, - "startIndex": 6358, - "textRun": { - "content": "modular content", - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.8, - "green": 0.33333334, - "red": 0.06666667 - } - } - }, - "link": { - "url": "https://www.contentful.com/blog/modular-content/" - }, - "underline": true, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 6540, - "startIndex": 6373, - "textRun": { - "content": " and governance capabilities enabled them to keep brand messaging consistent across dealerships while giving each dealership the flexibility to add personal touches. \n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 6264 - }, - { - "endIndex": 6541, - "paragraph": { - "elements": [ - { - "endIndex": 6541, - "startIndex": 6540, - "textRun": { - "content": "\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 6540 - }, - { - "endIndex": 6739, - "paragraph": { - "elements": [ - { - "endIndex": 6706, - "startIndex": 6541, - "textRun": { - "content": "The result is a digital experience that feels as polished and reliable as walking into a showroom \u2014 and it\u2019s paying off with higher customer engagement, including a ", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 6737, - "startIndex": 6706, - "textRun": { - "content": "47% jump in test drive bookings", - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.8, - "green": 0.33333334, - "red": 0.06666667 - } - } - }, - "link": { - "url": "https://www.contentful.com/case-studies/bmw-tmwx/" - }, - "underline": true, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 6738, - "startIndex": 6737, - "textRun": { - "content": ".", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 6739, - "startIndex": 6738, - "textRun": { - "content": "\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 6541 - }, - { - "endIndex": 6740, - "paragraph": { - "elements": [ - { - "endIndex": 6740, - "startIndex": 6739, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 6739 - }, - { - "endIndex": 6812, - "paragraph": { - "elements": [ - { - "endIndex": 6812, - "startIndex": 6740, - "textRun": { - "content": "Contentful Personalization lets Ruggable\u2019s small team drive big results\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "headingId": "h.o34ya2w0kduu", - "namedStyleType": "HEADING_3" - } - }, - "startIndex": 6740 - }, - { - "endIndex": 7346, - "paragraph": { - "elements": [ - { - "endIndex": 7233, - "startIndex": 6812, - "textRun": { - "content": "Ruggable shows how easy it can be to get the benefits of a DXP, even if you have a small team. Ahead of Black Friday, their team used Contentful to quickly spin up modular content components, connect Shopify data, and schedule promotions without heavy developer support. What once took nerve-racking code changes was handled by just three people in four weeks. The payoff was immediate: campaigns went live smoothly, and ", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 7248, - "startIndex": 7233, - "textRun": { - "content": "personalization", - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.8, - "green": 0.33333334, - "red": 0.06666667 - } - } - }, - "link": { - "url": "https://www.contentful.com/products/personalization/" - }, - "underline": true, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 7279, - "startIndex": 7248, - "textRun": { - "content": " powered by Contentful drove a ", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 7344, - "startIndex": 7279, - "textRun": { - "content": "7x increase in click-through rates and a 25% boost in conversions", - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.8, - "green": 0.33333334, - "red": 0.06666667 - } - } - }, - "link": { - "url": "https://www.contentful.com/case-studies/ruggable/" - }, - "underline": true, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 7345, - "startIndex": 7344, - "textRun": { - "content": ".", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 7346, - "startIndex": 7345, - "textRun": { - "content": "\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 6812 - }, - { - "endIndex": 7347, - "paragraph": { - "elements": [ - { - "endIndex": 7347, - "startIndex": 7346, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 7346 - }, - { - "endIndex": 7392, - "paragraph": { - "elements": [ - { - "endIndex": 7391, - "startIndex": 7347, - "textRun": { - "content": "A Modern DXP plus AI is a slam dunk for FIBA", - "textStyle": {} - } - }, - { - "endIndex": 7392, - "startIndex": 7391, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "headingId": "h.92t9jbuy2o48", - "namedStyleType": "HEADING_3" - } - }, - "startIndex": 7347 - }, - { - "endIndex": 7714, - "paragraph": { - "elements": [ - { - "endIndex": 7417, - "startIndex": 7392, - "textRun": { - "content": "Contentful made it easy f", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 7714, - "startIndex": 7417, - "textRun": { - "content": "or the International Basketball Federation (FIBA) to scale digital experiences across a global stage. \u201cContentful helps us turn things out like a factory \u2014 we can easily create a website for each event, even if there are 80+ of them in a given year,\u201d shared Yann Th\u00e9z\u00e9nas, Project Manager at FIBA\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 12, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 12, - "unit": "PT" - } - } - }, - "startIndex": 7392 - }, - { - "endIndex": 7977, - "paragraph": { - "elements": [ - { - "endIndex": 7873, - "startIndex": 7714, - "textRun": { - "content": "That efficiency means fans around the world get timely, localized updates, even during high-pressure live events. Looking ahead, FIBA is beginning to leverage ", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 7895, - "startIndex": 7873, - "textRun": { - "content": "Contentful Marketplace", - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.8, - "green": 0.33333334, - "red": 0.06666667 - } - } - }, - "link": { - "url": "https://www.contentful.com/marketplace/" - }, - "underline": true, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 7910, - "startIndex": 7895, - "textRun": { - "content": " apps like the ", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 7940, - "startIndex": 7910, - "textRun": { - "content": "AI Content Generator by OpenAI", - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.8, - "green": 0.33333334, - "red": 0.06666667 - } - } - }, - "link": { - "url": "https://www.contentful.com/marketplace/ai-content-generator/" - }, - "underline": true, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 7977, - "startIndex": 7940, - "textRun": { - "content": " to speed up and scale translations.\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 12, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 12, - "unit": "PT" - } - } - }, - "startIndex": 7714 - }, - { - "endIndex": 8072, - "paragraph": { - "elements": [ - { - "endIndex": 7993, - "startIndex": 7977, - "textRun": { - "content": "Check out these ", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 8004, - "startIndex": 7993, - "textRun": { - "content": "demo videos", - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.8, - "green": 0.33333334, - "red": 0.06666667 - } - } - }, - "link": { - "url": "https://www.contentful.com/resources/product-demo-videos/" - }, - "underline": true, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 8072, - "startIndex": 8004, - "textRun": { - "content": " to learn more about getting the benefits of a DXP from Contentful.\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 7977 - }, - { - "endIndex": 8073, - "paragraph": { - "elements": [ - { - "endIndex": 8073, - "startIndex": 8072, - "textRun": { - "content": "\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 8072 - }, - { - "endIndex": 8124, - "paragraph": { - "elements": [ - { - "endIndex": 8124, - "startIndex": 8073, - "textRun": { - "content": "Choose a digital experience platform that fits you\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "headingId": "h.c0v7vqvd7bv9", - "namedStyleType": "HEADING_2" - } - }, - "startIndex": 8073 - }, - { - "endIndex": 8196, - "paragraph": { - "elements": [ - { - "endIndex": 8196, - "startIndex": 8124, - "textRun": { - "content": "Every business is unique. You know what works for you and what doesn\u2019t.\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 8124 - }, - { - "endIndex": 8197, - "paragraph": { - "elements": [ - { - "endIndex": 8197, - "startIndex": 8196, - "textRun": { - "content": "\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 8196 - }, - { - "endIndex": 8402, - "paragraph": { - "elements": [ - { - "endIndex": 8402, - "startIndex": 8197, - "textRun": { - "content": "A composable DXP respects that, enabling you to pick and choose the components you need, connect them to the parts of your system that you already like, and make changes without breaking the entire stack.\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 8197 - }, - { - "endIndex": 8403, - "paragraph": { - "elements": [ - { - "endIndex": 8403, - "startIndex": 8402, - "textRun": { - "content": "\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 8402 - }, - { - "endIndex": 8583, - "paragraph": { - "elements": [ - { - "endIndex": 8583, - "startIndex": 8403, - "textRun": { - "content": "Don\u2019t trust your future to outdated platforms. Contentful is a modern DXP with a proven record of achieving measurable results, which you\u2019ll happily share with the executive team.\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 8403 - }, - { - "endIndex": 8584, - "paragraph": { - "elements": [ - { - "endIndex": 8584, - "startIndex": 8583, - "textRun": { - "content": "\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 8583 - }, - { - "endIndex": 8711, - "paragraph": { - "elements": [ - { - "endIndex": 8672, - "startIndex": 8584, - "textRun": { - "content": "See how Contentful can help you get the full benefits of a DXP built for your business. ", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 8710, - "startIndex": 8672, - "textRun": { - "content": "Contact sales for a personalized demo.", - "textStyle": { - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.8, - "green": 0.33333334, - "red": 0.06666667 - } - } - }, - "link": { - "url": "https://www.contentful.com/contact/sales/" - }, - "underline": true, - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - }, - { - "endIndex": 8711, - "startIndex": 8710, - "textRun": { - "content": "\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 8584 - }, - { - "endIndex": 8712, - "paragraph": { - "elements": [ - { - "endIndex": 8712, - "startIndex": 8711, - "textRun": { - "content": "\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 8711 - }, - { - "endIndex": 8713, - "paragraph": { - "elements": [ - { - "endIndex": 8713, - "startIndex": 8712, - "textRun": { - "content": "\n", - "textStyle": { - "weightedFontFamily": { - "fontFamily": "Red Hat Display", - "weight": 400 - } - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 24, - "unit": "PT" - } - } - }, - "startIndex": 8712 - } - ] - }, - "documentStyle": { - "background": { - "color": {} - }, - "documentFormat": { - "documentMode": "PAGES" - }, - "marginBottom": { - "magnitude": 72, - "unit": "PT" - }, - "marginFooter": { - "magnitude": 36, - "unit": "PT" - }, - "marginHeader": { - "magnitude": 36, - "unit": "PT" - }, - "marginLeft": { - "magnitude": 72, - "unit": "PT" - }, - "marginRight": { - "magnitude": 72, - "unit": "PT" - }, - "marginTop": { - "magnitude": 72, - "unit": "PT" - }, - "pageNumberStart": 1, - "pageSize": { - "height": { - "magnitude": 792, - "unit": "PT" - }, - "width": { - "magnitude": 612, - "unit": "PT" - } - }, - "useCustomHeaderFooterMargins": true - }, - "inlineObjects": { - "kix.nbcfdc81186i": { - "inlineObjectProperties": { - "embeddedObject": { - "embeddedObjectBorder": { - "color": { - "color": { - "rgbColor": {} - } - }, - "dashStyle": "SOLID", - "propertyState": "NOT_RENDERED", - "width": { - "unit": "PT" - } - }, - "imageProperties": { - "contentUri": "https://lh7-rt.googleusercontent.com/docsz/AD_4nXcT8c4_Feuc71F1U8El-tesEhOk-fWeyKoPtafbMxM9bbmiVobgvbDLWM3EOMP516uLaSpEsWlhHpaoN8h6hpTzAnYGywb_L8w0A1DIC4dKz1MqdLaR09WIIIW4yJcNDqNLYPAcZqoWGNUq3tzwEvn6Cw?key=OBTs76szDX38nr7NbRjhXA", - "cropProperties": {} - }, - "marginBottom": { - "magnitude": 9, - "unit": "PT" - }, - "marginLeft": { - "magnitude": 9, - "unit": "PT" - }, - "marginRight": { - "magnitude": 9, - "unit": "PT" - }, - "marginTop": { - "magnitude": 9, - "unit": "PT" - }, - "size": { - "height": { - "magnitude": 195, - "unit": "PT" - }, - "width": { - "magnitude": 346.5, - "unit": "PT" - } - } - } - }, - "objectId": "kix.nbcfdc81186i" - } - }, - "lists": { - "kix.8x8klvcu13zf": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphFormat": "%1", - "glyphSymbol": "\u25cb", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphFormat": "%2", - "glyphSymbol": "\u25a0", - "indentFirstLine": { - "magnitude": 90, - "unit": "PT" - }, - "indentStart": { - "magnitude": 108, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphFormat": "%3", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "magnitude": 126, - "unit": "PT" - }, - "indentStart": { - "magnitude": 144, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphFormat": "%4", - "glyphSymbol": "\u25cb", - "indentFirstLine": { - "magnitude": 162, - "unit": "PT" - }, - "indentStart": { - "magnitude": 180, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphFormat": "%5", - "glyphSymbol": "\u25a0", - "indentFirstLine": { - "magnitude": 198, - "unit": "PT" - }, - "indentStart": { - "magnitude": 216, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphFormat": "%6", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "magnitude": 234, - "unit": "PT" - }, - "indentStart": { - "magnitude": 252, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphFormat": "%7", - "glyphSymbol": "\u25cb", - "indentFirstLine": { - "magnitude": 270, - "unit": "PT" - }, - "indentStart": { - "magnitude": 288, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphFormat": "%8", - "glyphSymbol": "\u25a0", - "indentFirstLine": { - "magnitude": 306, - "unit": "PT" - }, - "indentStart": { - "magnitude": 324, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - } - ] - } - }, - "kix.vdrltauyvi44": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphFormat": "%1.", - "glyphType": "ALPHA", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "END", - "glyphFormat": "%2.", - "glyphType": "ROMAN", - "indentFirstLine": { - "magnitude": 90, - "unit": "PT" - }, - "indentStart": { - "magnitude": 108, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphFormat": "%3.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "magnitude": 126, - "unit": "PT" - }, - "indentStart": { - "magnitude": 144, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphFormat": "%4.", - "glyphType": "ALPHA", - "indentFirstLine": { - "magnitude": 162, - "unit": "PT" - }, - "indentStart": { - "magnitude": 180, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "END", - "glyphFormat": "%5.", - "glyphType": "ROMAN", - "indentFirstLine": { - "magnitude": 198, - "unit": "PT" - }, - "indentStart": { - "magnitude": 216, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphFormat": "%6.", - "glyphType": "DECIMAL", - "indentFirstLine": { - "magnitude": 234, - "unit": "PT" - }, - "indentStart": { - "magnitude": 252, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphFormat": "%7.", - "glyphType": "ALPHA", - "indentFirstLine": { - "magnitude": 270, - "unit": "PT" - }, - "indentStart": { - "magnitude": 288, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "END", - "glyphFormat": "%8.", - "glyphType": "ROMAN", - "indentFirstLine": { - "magnitude": 306, - "unit": "PT" - }, - "indentStart": { - "magnitude": 324, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - } - ] - } - }, - "kix.xn14b7z3imo2": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphFormat": "%0", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphFormat": "%1", - "glyphSymbol": "\u25cb", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphFormat": "%2", - "glyphSymbol": "\u25a0", - "indentFirstLine": { - "magnitude": 90, - "unit": "PT" - }, - "indentStart": { - "magnitude": 108, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphFormat": "%3", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "magnitude": 126, - "unit": "PT" - }, - "indentStart": { - "magnitude": 144, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphFormat": "%4", - "glyphSymbol": "\u25cb", - "indentFirstLine": { - "magnitude": 162, - "unit": "PT" - }, - "indentStart": { - "magnitude": 180, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphFormat": "%5", - "glyphSymbol": "\u25a0", - "indentFirstLine": { - "magnitude": 198, - "unit": "PT" - }, - "indentStart": { - "magnitude": 216, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphFormat": "%6", - "glyphSymbol": "\u25cf", - "indentFirstLine": { - "magnitude": 234, - "unit": "PT" - }, - "indentStart": { - "magnitude": 252, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphFormat": "%7", - "glyphSymbol": "\u25cb", - "indentFirstLine": { - "magnitude": 270, - "unit": "PT" - }, - "indentStart": { - "magnitude": 288, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphFormat": "%8", - "glyphSymbol": "\u25a0", - "indentFirstLine": { - "magnitude": 306, - "unit": "PT" - }, - "indentStart": { - "magnitude": 324, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - } - ] - } - } - }, - "namedStyles": { - "styles": [ - { - "namedStyleType": "NORMAL_TEXT", - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": true, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 115, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "spacingMode": "COLLAPSE_LISTS" - }, - "textStyle": { - "backgroundColor": {}, - "baselineOffset": "NONE", - "bold": false, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "italic": false, - "smallCaps": false, - "strikethrough": false, - "underline": false, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_1", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "spaceAbove": { - "magnitude": 20, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 6, - "unit": "PT" - } - }, - "textStyle": { - "fontSize": { - "magnitude": 20, - "unit": "PT" - } - } - }, - { - "namedStyleType": "HEADING_2", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "spaceAbove": { - "magnitude": 18, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 6, - "unit": "PT" - } - }, - "textStyle": { - "bold": false, - "fontSize": { - "magnitude": 16, - "unit": "PT" - } - } - }, - { - "namedStyleType": "HEADING_3", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "spaceAbove": { - "magnitude": 16, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 4, - "unit": "PT" - } - }, - "textStyle": { - "bold": false, - "fontSize": { - "magnitude": 14, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.2627451, - "green": 0.2627451, - "red": 0.2627451 - } - } - } - } - }, - { - "namedStyleType": "HEADING_4", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "spaceAbove": { - "magnitude": 14, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 4, - "unit": "PT" - } - }, - "textStyle": { - "fontSize": { - "magnitude": 12, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.4, - "green": 0.4, - "red": 0.4 - } - } - } - } - }, - { - "namedStyleType": "HEADING_5", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "spaceAbove": { - "magnitude": 12, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 4, - "unit": "PT" - } - }, - "textStyle": { - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.4, - "green": 0.4, - "red": 0.4 - } - } - } - } - }, - { - "namedStyleType": "HEADING_6", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "spaceAbove": { - "magnitude": 12, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 4, - "unit": "PT" - } - }, - "textStyle": { - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.4, - "green": 0.4, - "red": 0.4 - } - } - }, - "italic": true - } - }, - { - "namedStyleType": "TITLE", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 3, - "unit": "PT" - } - }, - "textStyle": { - "fontSize": { - "magnitude": 26, - "unit": "PT" - } - } - }, - { - "namedStyleType": "SUBTITLE", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 16, - "unit": "PT" - } - }, - "textStyle": { - "fontSize": { - "magnitude": 15, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.4, - "green": 0.4, - "red": 0.4 - } - } - }, - "italic": false, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - } - } - } - ] - } - }, - "tabProperties": { - "index": 0, - "tabId": "t.0", - "title": "Draft" - } - }, - { - "documentTab": { - "body": { - "content": [ - { - "endIndex": 1, - "sectionBreak": { - "sectionStyle": { - "columnSeparatorStyle": "NONE", - "contentDirection": "LEFT_TO_RIGHT", - "sectionType": "CONTINUOUS" - } - } - }, - { - "endIndex": 111, - "paragraph": { - "elements": [ - { - "endIndex": 111, - "startIndex": 1, - "textRun": { - "content": "Here are some directions for a graphic designer to create a key visual based on the blog post \"DXP benefits\":\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 1 - }, - { - "endIndex": 112, - "paragraph": { - "elements": [ - { - "endIndex": 112, - "startIndex": 111, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 111 - }, - { - "endIndex": 157, - "paragraph": { - "elements": [ - { - "endIndex": 157, - "startIndex": 112, - "textRun": { - "content": "Key Visual Concept: The Composable Advantage\n", - "textStyle": { - "bold": true - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 112 - }, - { - "endIndex": 158, - "paragraph": { - "elements": [ - { - "endIndex": 158, - "startIndex": 157, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 157 - }, - { - "endIndex": 364, - "paragraph": { - "elements": [ - { - "endIndex": 172, - "startIndex": 158, - "textRun": { - "content": "Overall Theme:", - "textStyle": { - "bold": true - } - } - }, - { - "endIndex": 364, - "startIndex": 172, - "textRun": { - "content": " The visual should convey a sense of flexibility, integration, and future-readiness, highlighting how a composable DXP empowers businesses to build tailored and adaptable digital experiences.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 158 - }, - { - "endIndex": 365, - "paragraph": { - "elements": [ - { - "endIndex": 365, - "startIndex": 364, - "textRun": { - "content": "\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 364 - }, - { - "endIndex": 390, - "paragraph": { - "elements": [ - { - "endIndex": 390, - "startIndex": 365, - "textRun": { - "content": "Elements to Incorporate:\n", - "textStyle": { - "bold": true - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 365 - }, - { - "endIndex": 662, - "paragraph": { - "bullet": { - "listId": "kix.lz2y53co32x0", - "textStyle": {} - }, - "elements": [ - { - "endIndex": 415, - "startIndex": 390, - "textRun": { - "content": "Modular & Interconnected:", - "textStyle": { - "bold": true - } - } - }, - { - "endIndex": 662, - "startIndex": 415, - "textRun": { - "content": " Represent the \"composable\" aspect with distinct, yet seamlessly connected, modules or building blocks. These could be abstract shapes, icons, or even subtle representations of different tools (e.g., a small gear, a chat bubble, a document icon).\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 12, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 12, - "unit": "PT" - } - } - }, - "startIndex": 390 - }, - { - "endIndex": 870, - "paragraph": { - "bullet": { - "listId": "kix.lz2y53co32x0", - "textStyle": {} - }, - "elements": [ - { - "endIndex": 680, - "startIndex": 662, - "textRun": { - "content": "Growth & Progress:", - "textStyle": { - "bold": true - } - } - }, - { - "endIndex": 870, - "startIndex": 680, - "textRun": { - "content": " Show a clear upward or forward trajectory, symbolizing increased productivity, speed, and agility. This could be a subtle gradient, an arrow motif, or elements that appear to be expanding.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 12, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 12, - "unit": "PT" - } - } - }, - "startIndex": 662 - }, - { - "endIndex": 1105, - "paragraph": { - "bullet": { - "listId": "kix.lz2y53co32x0", - "textStyle": {} - }, - "elements": [ - { - "endIndex": 891, - "startIndex": 870, - "textRun": { - "content": "Central Hub/Platform:", - "textStyle": { - "bold": true - } - } - }, - { - "endIndex": 1105, - "startIndex": 891, - "textRun": { - "content": " A central, unifying element that demonstrates how Contentful acts as the core, connecting all the composable parts. This could be a subtle glow, a central node, or a foundational element from which others extend.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 12, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 12, - "unit": "PT" - } - } - }, - "startIndex": 870 - }, - { - "endIndex": 1327, - "paragraph": { - "bullet": { - "listId": "kix.lz2y53co32x0", - "textStyle": {} - }, - "elements": [ - { - "endIndex": 1139, - "startIndex": 1105, - "textRun": { - "content": "Digital Experience/Customer Focus:", - "textStyle": { - "bold": true - } - } - }, - { - "endIndex": 1327, - "startIndex": 1139, - "textRun": { - "content": " Hint at the \"digital experience\" aspect without being overly literal. This could be represented by subtle digital patterns, a faint outline of a device screen, or a sense of interaction.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 12, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 12, - "unit": "PT" - } - } - }, - "startIndex": 1105 - }, - { - "endIndex": 1453, - "paragraph": { - "bullet": { - "listId": "kix.lz2y53co32x0", - "textStyle": {} - }, - "elements": [ - { - "endIndex": 1354, - "startIndex": 1327, - "textRun": { - "content": "Brand Consistency (Subtle):", - "textStyle": { - "bold": true - } - } - }, - { - "endIndex": 1453, - "startIndex": 1354, - "textRun": { - "content": " A consistent color palette or visual style across the modules to subtly convey brand consistency.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 12, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 12, - "unit": "PT" - } - } - }, - "startIndex": 1327 - }, - { - "endIndex": 1676, - "paragraph": { - "bullet": { - "listId": "kix.lz2y53co32x0", - "textStyle": {} - }, - "elements": [ - { - "endIndex": 1477, - "startIndex": 1453, - "textRun": { - "content": "Future-Ready/Innovation:", - "textStyle": { - "bold": true - } - } - }, - { - "endIndex": 1676, - "startIndex": 1477, - "textRun": { - "content": " Incorporate subtle elements that suggest innovation and readiness for future technologies like AI. This could be a faint futuristic glow, abstract data streams, or a hint of AI-related iconography.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 12, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 12, - "unit": "PT" - } - } - }, - "startIndex": 1453 - }, - { - "endIndex": 1691, - "paragraph": { - "elements": [ - { - "endIndex": 1691, - "startIndex": 1676, - "textRun": { - "content": "Color Palette:\n", - "textStyle": { - "bold": true - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 1676 - }, - { - "endIndex": 1927, - "paragraph": { - "bullet": { - "listId": "kix.sfo7zdq8bsnk", - "textStyle": {} - }, - "elements": [ - { - "endIndex": 1927, - "startIndex": 1691, - "textRun": { - "content": "Consider a modern, professional, and slightly vibrant color palette that reflects innovation and growth. Avoid overly corporate or dull colors. Perhaps a primary brand color with complementary secondary colors for the modular elements.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 12, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 12, - "unit": "PT" - } - } - }, - "startIndex": 1691 - }, - { - "endIndex": 1942, - "paragraph": { - "elements": [ - { - "endIndex": 1942, - "startIndex": 1927, - "textRun": { - "content": "Imagery/Style:\n", - "textStyle": { - "bold": true - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 1927 - }, - { - "endIndex": 2067, - "paragraph": { - "bullet": { - "listId": "kix.yvnb3ezfmvk3", - "textStyle": {} - }, - "elements": [ - { - "endIndex": 1961, - "startIndex": 1942, - "textRun": { - "content": "Abstract and Clean:", - "textStyle": { - "bold": true - } - } - }, - { - "endIndex": 2067, - "startIndex": 1961, - "textRun": { - "content": " Lean towards abstract, clean, and modern graphics rather than realistic or overly complex illustrations.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 12, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 12, - "unit": "PT" - } - } - }, - "startIndex": 1942 - }, - { - "endIndex": 2173, - "paragraph": { - "bullet": { - "listId": "kix.yvnb3ezfmvk3", - "textStyle": {} - }, - "elements": [ - { - "endIndex": 2087, - "startIndex": 2067, - "textRun": { - "content": "Dynamic Composition:", - "textStyle": { - "bold": true - } - } - }, - { - "endIndex": 2173, - "startIndex": 2087, - "textRun": { - "content": " Create a dynamic composition that draws the eye through the interconnected elements.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 12, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 12, - "unit": "PT" - } - } - }, - "startIndex": 2067 - }, - { - "endIndex": 2279, - "paragraph": { - "bullet": { - "listId": "kix.yvnb3ezfmvk3", - "textStyle": {} - }, - "elements": [ - { - "endIndex": 2186, - "startIndex": 2173, - "textRun": { - "content": "Subtle Depth:", - "textStyle": { - "bold": true - } - } - }, - { - "endIndex": 2279, - "startIndex": 2186, - "textRun": { - "content": " Use subtle shadows or gradients to give the visual some depth without making it feel heavy.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 12, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 12, - "unit": "PT" - } - } - }, - "startIndex": 2173 - }, - { - "endIndex": 2336, - "paragraph": { - "elements": [ - { - "endIndex": 2336, - "startIndex": 2279, - "textRun": { - "content": "Examples of Inspiration (Conceptual, not direct copies):\n", - "textStyle": { - "bold": true - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 2279 - }, - { - "endIndex": 2389, - "paragraph": { - "bullet": { - "listId": "kix.1tfofde9qvse", - "textStyle": {} - }, - "elements": [ - { - "endIndex": 2389, - "startIndex": 2336, - "textRun": { - "content": "Infographics that illustrate interconnected systems.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 12, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 12, - "unit": "PT" - } - } - }, - "startIndex": 2336 - }, - { - "endIndex": 2451, - "paragraph": { - "bullet": { - "listId": "kix.1tfofde9qvse", - "textStyle": {} - }, - "elements": [ - { - "endIndex": 2451, - "startIndex": 2389, - "textRun": { - "content": "Abstract representations of data flow or network connections.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 12, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 12, - "unit": "PT" - } - } - }, - "startIndex": 2389 - }, - { - "endIndex": 2525, - "paragraph": { - "bullet": { - "listId": "kix.1tfofde9qvse", - "textStyle": {} - }, - "elements": [ - { - "endIndex": 2525, - "startIndex": 2451, - "textRun": { - "content": "Modern tech company branding that emphasizes flexibility and integration.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 12, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 12, - "unit": "PT" - } - } - }, - "startIndex": 2451 - }, - { - "endIndex": 2532, - "paragraph": { - "elements": [ - { - "endIndex": 2532, - "startIndex": 2525, - "textRun": { - "content": "Avoid:\n", - "textStyle": { - "bold": true - } - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 2525 - }, - { - "endIndex": 2591, - "paragraph": { - "bullet": { - "listId": "kix.wiitwhreylki", - "textStyle": {} - }, - "elements": [ - { - "endIndex": 2591, - "startIndex": 2532, - "textRun": { - "content": "Overly literal depictions of specific software interfaces.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 12, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 12, - "unit": "PT" - } - } - }, - "startIndex": 2532 - }, - { - "endIndex": 2670, - "paragraph": { - "bullet": { - "listId": "kix.wiitwhreylki", - "textStyle": {} - }, - "elements": [ - { - "endIndex": 2670, - "startIndex": 2591, - "textRun": { - "content": "Cluttered or chaotic designs that contradict the idea of seamless integration.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 12, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 12, - "unit": "PT" - } - } - }, - "startIndex": 2591 - }, - { - "endIndex": 2705, - "paragraph": { - "bullet": { - "listId": "kix.wiitwhreylki", - "textStyle": {} - }, - "elements": [ - { - "endIndex": 2705, - "startIndex": 2670, - "textRun": { - "content": "Outdated or generic stock imagery.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "namedStyleType": "NORMAL_TEXT", - "spaceAbove": { - "magnitude": 12, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 12, - "unit": "PT" - } - } - }, - "startIndex": 2670 - }, - { - "endIndex": 2876, - "paragraph": { - "elements": [ - { - "endIndex": 2724, - "startIndex": 2705, - "textRun": { - "content": "Overall Impression:", - "textStyle": { - "bold": true - } - } - }, - { - "endIndex": 2876, - "startIndex": 2724, - "textRun": { - "content": " The key visual should feel sophisticated, forward-thinking, and clearly communicate the benefits of a composable DXP in an engaging and memorable way.\n", - "textStyle": {} - } - } - ], - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "namedStyleType": "NORMAL_TEXT" - } - }, - "startIndex": 2705 - } - ] - }, - "documentStyle": { - "background": { - "color": {} - }, - "documentFormat": { - "documentMode": "PAGES" - }, - "marginBottom": { - "magnitude": 72, - "unit": "PT" - }, - "marginFooter": { - "magnitude": 36, - "unit": "PT" - }, - "marginHeader": { - "magnitude": 36, - "unit": "PT" - }, - "marginLeft": { - "magnitude": 72, - "unit": "PT" - }, - "marginRight": { - "magnitude": 72, - "unit": "PT" - }, - "marginTop": { - "magnitude": 72, - "unit": "PT" - }, - "pageNumberStart": 1, - "pageSize": { - "height": { - "magnitude": 792, - "unit": "PT" - }, - "width": { - "magnitude": 612, - "unit": "PT" - } - }, - "useCustomHeaderFooterMargins": true - }, - "lists": { - "kix.1tfofde9qvse": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 90, - "unit": "PT" - }, - "indentStart": { - "magnitude": 108, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 126, - "unit": "PT" - }, - "indentStart": { - "magnitude": 144, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 162, - "unit": "PT" - }, - "indentStart": { - "magnitude": 180, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 198, - "unit": "PT" - }, - "indentStart": { - "magnitude": 216, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 234, - "unit": "PT" - }, - "indentStart": { - "magnitude": 252, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 270, - "unit": "PT" - }, - "indentStart": { - "magnitude": 288, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 306, - "unit": "PT" - }, - "indentStart": { - "magnitude": 324, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - } - ] - } - }, - "kix.lz2y53co32x0": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 90, - "unit": "PT" - }, - "indentStart": { - "magnitude": 108, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 126, - "unit": "PT" - }, - "indentStart": { - "magnitude": 144, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 162, - "unit": "PT" - }, - "indentStart": { - "magnitude": 180, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 198, - "unit": "PT" - }, - "indentStart": { - "magnitude": 216, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 234, - "unit": "PT" - }, - "indentStart": { - "magnitude": 252, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 270, - "unit": "PT" - }, - "indentStart": { - "magnitude": 288, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 306, - "unit": "PT" - }, - "indentStart": { - "magnitude": 324, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - } - ] - } - }, - "kix.sfo7zdq8bsnk": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 90, - "unit": "PT" - }, - "indentStart": { - "magnitude": 108, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 126, - "unit": "PT" - }, - "indentStart": { - "magnitude": 144, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 162, - "unit": "PT" - }, - "indentStart": { - "magnitude": 180, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 198, - "unit": "PT" - }, - "indentStart": { - "magnitude": 216, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 234, - "unit": "PT" - }, - "indentStart": { - "magnitude": 252, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 270, - "unit": "PT" - }, - "indentStart": { - "magnitude": 288, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 306, - "unit": "PT" - }, - "indentStart": { - "magnitude": 324, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - } - ] - } - }, - "kix.wiitwhreylki": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 90, - "unit": "PT" - }, - "indentStart": { - "magnitude": 108, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 126, - "unit": "PT" - }, - "indentStart": { - "magnitude": 144, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 162, - "unit": "PT" - }, - "indentStart": { - "magnitude": 180, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 198, - "unit": "PT" - }, - "indentStart": { - "magnitude": 216, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 234, - "unit": "PT" - }, - "indentStart": { - "magnitude": 252, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 270, - "unit": "PT" - }, - "indentStart": { - "magnitude": 288, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 306, - "unit": "PT" - }, - "indentStart": { - "magnitude": 324, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - } - ] - } - }, - "kix.yvnb3ezfmvk3": { - "listProperties": { - "nestingLevels": [ - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 18, - "unit": "PT" - }, - "indentStart": { - "magnitude": 36, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 54, - "unit": "PT" - }, - "indentStart": { - "magnitude": 72, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 90, - "unit": "PT" - }, - "indentStart": { - "magnitude": 108, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 126, - "unit": "PT" - }, - "indentStart": { - "magnitude": 144, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 162, - "unit": "PT" - }, - "indentStart": { - "magnitude": 180, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 198, - "unit": "PT" - }, - "indentStart": { - "magnitude": 216, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 234, - "unit": "PT" - }, - "indentStart": { - "magnitude": 252, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 270, - "unit": "PT" - }, - "indentStart": { - "magnitude": 288, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - }, - { - "bulletAlignment": "START", - "glyphType": "GLYPH_TYPE_UNSPECIFIED", - "indentFirstLine": { - "magnitude": 306, - "unit": "PT" - }, - "indentStart": { - "magnitude": 324, - "unit": "PT" - }, - "startNumber": 1, - "textStyle": { - "underline": false - } - } - ] - } - } - }, - "namedStyles": { - "styles": [ - { - "namedStyleType": "NORMAL_TEXT", - "paragraphStyle": { - "alignment": "START", - "avoidWidowAndOrphan": true, - "borderBetween": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderBottom": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderLeft": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderRight": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "borderTop": { - "color": {}, - "dashStyle": "SOLID", - "padding": { - "unit": "PT" - }, - "width": { - "unit": "PT" - } - }, - "direction": "LEFT_TO_RIGHT", - "indentEnd": { - "unit": "PT" - }, - "indentFirstLine": { - "unit": "PT" - }, - "indentStart": { - "unit": "PT" - }, - "keepLinesTogether": false, - "keepWithNext": false, - "lineSpacing": 115, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "shading": { - "backgroundColor": {} - }, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "unit": "PT" - }, - "spacingMode": "COLLAPSE_LISTS" - }, - "textStyle": { - "backgroundColor": {}, - "baselineOffset": "NONE", - "bold": false, - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": {} - } - }, - "italic": false, - "smallCaps": false, - "strikethrough": false, - "underline": false, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - } - } - }, - { - "namedStyleType": "HEADING_1", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "spaceAbove": { - "magnitude": 20, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 6, - "unit": "PT" - } - }, - "textStyle": { - "fontSize": { - "magnitude": 20, - "unit": "PT" - } - } - }, - { - "namedStyleType": "HEADING_2", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "spaceAbove": { - "magnitude": 18, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 6, - "unit": "PT" - } - }, - "textStyle": { - "bold": false, - "fontSize": { - "magnitude": 16, - "unit": "PT" - } - } - }, - { - "namedStyleType": "HEADING_3", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "spaceAbove": { - "magnitude": 16, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 4, - "unit": "PT" - } - }, - "textStyle": { - "bold": false, - "fontSize": { - "magnitude": 14, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.2627451, - "green": 0.2627451, - "red": 0.2627451 - } - } - } - } - }, - { - "namedStyleType": "HEADING_4", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "spaceAbove": { - "magnitude": 14, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 4, - "unit": "PT" - } - }, - "textStyle": { - "fontSize": { - "magnitude": 12, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.4, - "green": 0.4, - "red": 0.4 - } - } - } - } - }, - { - "namedStyleType": "HEADING_5", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "spaceAbove": { - "magnitude": 12, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 4, - "unit": "PT" - } - }, - "textStyle": { - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.4, - "green": 0.4, - "red": 0.4 - } - } - } - } - }, - { - "namedStyleType": "HEADING_6", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "spaceAbove": { - "magnitude": 12, - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 4, - "unit": "PT" - } - }, - "textStyle": { - "fontSize": { - "magnitude": 11, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.4, - "green": 0.4, - "red": 0.4 - } - } - }, - "italic": true - } - }, - { - "namedStyleType": "TITLE", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 3, - "unit": "PT" - } - }, - "textStyle": { - "fontSize": { - "magnitude": 26, - "unit": "PT" - } - } - }, - { - "namedStyleType": "SUBTITLE", - "paragraphStyle": { - "direction": "LEFT_TO_RIGHT", - "keepLinesTogether": true, - "keepWithNext": true, - "namedStyleType": "NORMAL_TEXT", - "pageBreakBefore": false, - "spaceAbove": { - "unit": "PT" - }, - "spaceBelow": { - "magnitude": 16, - "unit": "PT" - } - }, - "textStyle": { - "fontSize": { - "magnitude": 15, - "unit": "PT" - }, - "foregroundColor": { - "color": { - "rgbColor": { - "blue": 0.4, - "green": 0.4, - "red": 0.4 - } - } - }, - "italic": false, - "weightedFontFamily": { - "fontFamily": "Arial", - "weight": 400 - } - } - } - ] - } - }, - "tabProperties": { - "index": 1, - "tabId": "t.6pnw08u81r", - "title": "GPT Direction" - } - } - ], - "title": "Doc_8_DXP_benefits - Sample" -} \ No newline at end of file diff --git a/apps/google-docs/src/utils/test_docs_json/index.ts b/apps/google-docs/src/utils/test_docs_json/index.ts deleted file mode 100644 index 1cc46b8598..0000000000 --- a/apps/google-docs/src/utils/test_docs_json/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -// Import all test documents -import Doc1 from './Doc_1_Basic_Structure_Test.json'; -import Doc2 from './Doc_2_Rich_Text_Formatting_Test.json'; -import Doc3 from './Doc_3_Nested_Structures_And_References_Test.json'; -import Doc4 from './Doc_4_Media_Embeds_Test.json'; -import Doc5 from './Doc_5_Bulk_Entry_Stress_Test.json'; -import Doc6 from './Doc_6_Multilingual_Test.json'; -import Doc7 from './Doc_7_Edge_Cases_Test.json'; -import Doc8 from './Doc_8_DXP_benefits - Sample.json'; - -// Optional import: Doc9 may not exist in all environments (e.g., S3 hosted app) -const doc9Modules = (import.meta as any).glob('./Doc_9_Customer_Example_Doc.json', { eager: true }); -const Doc9 = doc9Modules?.['./Doc_9_Customer_Example_Doc.json']?.default || null; - -// Export test documents array -export const TEST_DOCUMENTS = [ - { id: 'doc1', title: 'Doc 1: Basic Structure Test', data: Doc1 }, - { id: 'doc2', title: 'Doc 2: Rich Text Formatting Test', data: Doc2 }, - { id: 'doc3', title: 'Doc 3: Nested Structures And References Test', data: Doc3 }, - { id: 'doc4', title: 'Doc 4: Media Embeds Test', data: Doc4 }, - { id: 'doc5', title: 'Doc 5: Bulk Entry Stress Test', data: Doc5 }, - { id: 'doc6', title: 'Doc 6: Multilingual Test', data: Doc6 }, - { id: 'doc7', title: 'Doc 7: Edge Cases Test', data: Doc7 }, - { id: 'doc8', title: 'Doc 8: DXP Benefits Sample', data: Doc8 }, - { id: 'doc9', title: 'Doc 9: Customer Example Doc', data: Doc9 }, -]; From 532ff7e468bae1ed9b84a5f1f365d9cf86dc5f92 Mon Sep 17 00:00:00 2001 From: ryunsong-contentful <124832189+ryunsong-contentful@users.noreply.github.com> Date: Thu, 18 Dec 2025 16:57:47 -0700 Subject: [PATCH 03/14] feat: rename functions and files to be consistent with each other (#10358) --- apps/google-docs/contentful-app-manifest.json | 12 +++--- .../contentTypeParser.agent.ts | 2 +- .../documentParser.agent.ts | 2 +- .../functions/agents/observer/observer.ts | 16 -------- .../createEntriesHandler.ts | 8 ++-- .../createContentTypesAnalysisHandler.ts} | 6 +-- .../createPreviewHandler.ts | 8 ++-- .../functions/service/entryService.test.ts | 38 +++++++++---------- .../functions/service/entryService.ts | 2 +- 9 files changed, 39 insertions(+), 55 deletions(-) delete mode 100644 apps/google-docs/functions/agents/observer/observer.ts rename apps/google-docs/functions/handlers/{ => createEntries}/createEntriesHandler.ts (83%) rename apps/google-docs/functions/handlers/{analyzeContentTypesHandler.ts => createPreview/createContentTypesAnalysisHandler.ts} (80%) rename apps/google-docs/functions/handlers/{ => createPreview}/createPreviewHandler.ts (82%) diff --git a/apps/google-docs/contentful-app-manifest.json b/apps/google-docs/contentful-app-manifest.json index 424e4c3c0a..08a08468e7 100644 --- a/apps/google-docs/contentful-app-manifest.json +++ b/apps/google-docs/contentful-app-manifest.json @@ -25,8 +25,8 @@ "id": "createPreview", "name": "Create Preview", "description": "Parses the Google Doc and creates preview entries", - "path": "functions/handlers/createPreviewHandler.js", - "entryFile": "functions/handlers/createPreviewHandler.ts", + "path": "functions/handlers/createPreview/createPreviewHandler.js", + "entryFile": "functions/handlers/createPreview/createPreviewHandler.ts", "allowNetworks": [ "https://api.openai.com", "*.googleapis.com", @@ -41,8 +41,8 @@ "id": "analyzeContentTypes", "name": "Analyze Content Types", "description": "Analyzes content type structure and relationships using AI.", - "path": "functions/handlers/analyzeContentTypesHandler.js", - "entryFile": "functions/handlers/analyzeContentTypesHandler.ts", + "path": "functions/handlers/createPreview/createContentTypesAnalysisHandler.js", + "entryFile": "functions/handlers/createPreview/createContentTypesAnalysisHandler.ts", "allowNetworks": [ "https://api.openai.com" ], @@ -54,8 +54,8 @@ "id": "createEntries", "name": "Create Entries", "description": "Creates entries in Contentful", - "path": "functions/handlers/createEntriesHandler.js", - "entryFile": "functions/handlers/createEntriesHandler.ts", + "path": "functions/handlers/createEntries/createEntriesHandler.js", + "entryFile": "functions/handlers/createEntries/createEntriesHandler.ts", "allowNetworks": [], "accepts": [ "appaction.call" diff --git a/apps/google-docs/functions/agents/contentTypeParserAgent/contentTypeParser.agent.ts b/apps/google-docs/functions/agents/contentTypeParserAgent/contentTypeParser.agent.ts index dac678548a..aa9bc2be1b 100644 --- a/apps/google-docs/functions/agents/contentTypeParserAgent/contentTypeParser.agent.ts +++ b/apps/google-docs/functions/agents/contentTypeParserAgent/contentTypeParser.agent.ts @@ -16,7 +16,7 @@ export interface ContentTypeParserConfig { * @returns Promise resolving to structured parse result with summaries * */ -export async function analyzeContentTypes({ +export async function createContentTypeAnalysisWithAgent({ contentTypes, openAiApiKey, }: ContentTypeParserConfig): Promise { diff --git a/apps/google-docs/functions/agents/documentParserAgent/documentParser.agent.ts b/apps/google-docs/functions/agents/documentParserAgent/documentParser.agent.ts index b8f5e2178b..8e6ce4f8b1 100644 --- a/apps/google-docs/functions/agents/documentParserAgent/documentParser.agent.ts +++ b/apps/google-docs/functions/agents/documentParserAgent/documentParser.agent.ts @@ -32,7 +32,7 @@ export interface DocumentParserConfig { * @param config - Parser configuration including API key, document JSON, and content types * @returns Promise resolving to entries ready for CMA client */ -export async function analyzeDocumentWithAgent( +export async function createPreviewWithAgent( config: DocumentParserConfig ): Promise { // TODO: Double check these values and make sure they are compatible because not every user will have a key diff --git a/apps/google-docs/functions/agents/observer/observer.ts b/apps/google-docs/functions/agents/observer/observer.ts deleted file mode 100644 index c823a651af..0000000000 --- a/apps/google-docs/functions/agents/observer/observer.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * INTEG-3260 Must be complete first before implementing INTEG-3261 - * INTEG-3261 Implement the observer. This will probably be broken down and done in pieces for the Document and Content Type parsers - */ - -export async function createContentTypeObservationsFromLLMResponse( - aiContentTypeResponse: any -): Promise { - throw new Error('Not implemented'); -} - -export async function createDocumentObservationsFromLLMResponse( - aiDocumentResponse: any -): Promise { - throw new Error('Not implemented'); -} diff --git a/apps/google-docs/functions/handlers/createEntriesHandler.ts b/apps/google-docs/functions/handlers/createEntries/createEntriesHandler.ts similarity index 83% rename from apps/google-docs/functions/handlers/createEntriesHandler.ts rename to apps/google-docs/functions/handlers/createEntries/createEntriesHandler.ts index 301d3f8079..afe1b6020d 100644 --- a/apps/google-docs/functions/handlers/createEntriesHandler.ts +++ b/apps/google-docs/functions/handlers/createEntries/createEntriesHandler.ts @@ -1,8 +1,8 @@ import { AppActionRequest, FunctionEventHandler } from '@contentful/node-apps-toolkit'; -import { createEntries } from '../service/entryService'; -import { initContentfulManagementClient } from '../service/initCMAClient'; -import { EntryToCreate } from '../agents/documentParserAgent/schema'; import { FunctionTypeEnum, FunctionEventContext } from '@contentful/node-apps-toolkit'; +import { EntryToCreate } from '../../agents/documentParserAgent/schema'; +import { createEntriesFromPreview } from '../../service/entryService'; +import { initContentfulManagementClient } from '../../service/initCMAClient'; interface CreateEntriesParameters { entries: EntryToCreate[]; @@ -38,7 +38,7 @@ export const handler: FunctionEventHandler< throw new Error('No matching content types found'); } - const result = await createEntries(cma, entries, { + const result = await createEntriesFromPreview(cma, entries, { spaceId: context.spaceId, environmentId: context.environmentId, contentTypes: contentTypes, diff --git a/apps/google-docs/functions/handlers/analyzeContentTypesHandler.ts b/apps/google-docs/functions/handlers/createPreview/createContentTypesAnalysisHandler.ts similarity index 80% rename from apps/google-docs/functions/handlers/analyzeContentTypesHandler.ts rename to apps/google-docs/functions/handlers/createPreview/createContentTypesAnalysisHandler.ts index 456ee64a52..4690e3d739 100644 --- a/apps/google-docs/functions/handlers/analyzeContentTypesHandler.ts +++ b/apps/google-docs/functions/handlers/createPreview/createContentTypesAnalysisHandler.ts @@ -4,9 +4,9 @@ import type { FunctionTypeEnum, AppActionRequest, } from '@contentful/node-apps-toolkit'; -import { analyzeContentTypes as analyzeContentTypesAgent } from '../agents/contentTypeParserAgent/contentTypeParser.agent'; -import { fetchContentTypes } from '../service/contentTypeService'; -import { initContentfulManagementClient } from '../service/initCMAClient'; +import { createContentTypeAnalysisWithAgent as analyzeContentTypesAgent } from '../../agents/contentTypeParserAgent/contentTypeParser.agent'; +import { fetchContentTypes } from '../../service/contentTypeService'; +import { initContentfulManagementClient } from '../../service/initCMAClient'; export type AnalyzeContentTypesParameters = { contentTypeIds: string[]; diff --git a/apps/google-docs/functions/handlers/createPreviewHandler.ts b/apps/google-docs/functions/handlers/createPreview/createPreviewHandler.ts similarity index 82% rename from apps/google-docs/functions/handlers/createPreviewHandler.ts rename to apps/google-docs/functions/handlers/createPreview/createPreviewHandler.ts index 864e31204e..1aab6ee7c5 100644 --- a/apps/google-docs/functions/handlers/createPreviewHandler.ts +++ b/apps/google-docs/functions/handlers/createPreview/createPreviewHandler.ts @@ -4,9 +4,9 @@ import type { FunctionTypeEnum, AppActionRequest, } from '@contentful/node-apps-toolkit'; -import { analyzeDocumentWithAgent } from '../agents/documentParserAgent/documentParser.agent'; -import { fetchContentTypes } from '../service/contentTypeService'; -import { initContentfulManagementClient } from '../service/initCMAClient'; +import { createPreviewWithAgent } from '../../agents/documentParserAgent/documentParser.agent'; +import { fetchContentTypes } from '../../service/contentTypeService'; +import { initContentfulManagementClient } from '../../service/initCMAClient'; export type CreatePreviewParameters = { contentTypeIds: string[]; @@ -44,7 +44,7 @@ export const handler: FunctionEventHandler< const contentTypes = await fetchContentTypes(cma, new Set(contentTypeIds)); // Process the document and create preview entries - const aiDocumentResponse = await analyzeDocumentWithAgent({ + const aiDocumentResponse = await createPreviewWithAgent({ documentId, oauthToken, openAiApiKey, diff --git a/apps/google-docs/functions/service/entryService.test.ts b/apps/google-docs/functions/service/entryService.test.ts index 051e39d4a3..89556524d3 100644 --- a/apps/google-docs/functions/service/entryService.test.ts +++ b/apps/google-docs/functions/service/entryService.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import type { PlainClientAPI, EntryProps, ContentTypeProps } from 'contentful-management'; -import { createEntries, EntryCreationResult } from './entryService'; +import { createEntriesFromPreview, EntryCreationResult } from './entryService'; import { EntryToCreate } from '../agents/documentParserAgent/schema'; // Mock CMA client @@ -40,7 +40,7 @@ describe('createEntries', () => { describe('Input Validation', () => { it('should reject null CMA client', async () => { - const result = await createEntries(null as any, [], { + const result = await createEntriesFromPreview(null as any, [], { spaceId: mockSpaceId, environmentId: mockEnvironmentId, contentTypes: mockContentTypes, @@ -52,7 +52,7 @@ describe('createEntries', () => { }); it('should reject undefined CMA client', async () => { - const result = await createEntries(undefined as any, [], { + const result = await createEntriesFromPreview(undefined as any, [], { spaceId: mockSpaceId, environmentId: mockEnvironmentId, contentTypes: mockContentTypes, @@ -64,7 +64,7 @@ describe('createEntries', () => { }); it('should reject null entries array', async () => { - const result = await createEntries(mockCMA, null as any, { + const result = await createEntriesFromPreview(mockCMA, null as any, { spaceId: mockSpaceId, environmentId: mockEnvironmentId, contentTypes: mockContentTypes, @@ -76,7 +76,7 @@ describe('createEntries', () => { }); it('should reject undefined entries array', async () => { - const result = await createEntries(mockCMA, undefined as any, { + const result = await createEntriesFromPreview(mockCMA, undefined as any, { spaceId: mockSpaceId, environmentId: mockEnvironmentId, contentTypes: mockContentTypes, @@ -88,7 +88,7 @@ describe('createEntries', () => { }); it('should reject empty entries array', async () => { - const result = await createEntries(mockCMA, [], { + const result = await createEntriesFromPreview(mockCMA, [], { spaceId: mockSpaceId, environmentId: mockEnvironmentId, contentTypes: mockContentTypes, @@ -100,7 +100,7 @@ describe('createEntries', () => { }); it('should reject non-array entries', async () => { - const result = await createEntries(mockCMA, {} as any, { + const result = await createEntriesFromPreview(mockCMA, {} as any, { spaceId: mockSpaceId, environmentId: mockEnvironmentId, contentTypes: mockContentTypes, @@ -119,7 +119,7 @@ describe('createEntries', () => { }, ]; - const result = await createEntries(mockCMA, entries, null as any); + const result = await createEntriesFromPreview(mockCMA, entries, null as any); expect(result.createdEntries).toHaveLength(0); expect(result.errors).toHaveLength(1); @@ -134,7 +134,7 @@ describe('createEntries', () => { }, ]; - const result = await createEntries(mockCMA, entries, { + const result = await createEntriesFromPreview(mockCMA, entries, { spaceId: '', environmentId: mockEnvironmentId, contentTypes: mockContentTypes, @@ -153,7 +153,7 @@ describe('createEntries', () => { }, ]; - const result = await createEntries(mockCMA, entries, { + const result = await createEntriesFromPreview(mockCMA, entries, { spaceId: mockSpaceId, environmentId: '', contentTypes: mockContentTypes, @@ -172,7 +172,7 @@ describe('createEntries', () => { }, ]; - const result = await createEntries(mockCMA, entries, { + const result = await createEntriesFromPreview(mockCMA, entries, { spaceId: mockSpaceId, environmentId: mockEnvironmentId, contentTypes: null as any, @@ -191,7 +191,7 @@ describe('createEntries', () => { }, ]; - const result = await createEntries(mockCMA, entries, { + const result = await createEntriesFromPreview(mockCMA, entries, { spaceId: mockSpaceId, environmentId: mockEnvironmentId, contentTypes: mockContentTypes, @@ -210,7 +210,7 @@ describe('createEntries', () => { }, ]; - const result = await createEntries(mockCMA, entries, { + const result = await createEntriesFromPreview(mockCMA, entries, { spaceId: mockSpaceId, environmentId: mockEnvironmentId, contentTypes: mockContentTypes, @@ -229,7 +229,7 @@ describe('createEntries', () => { }, ]; - const result = await createEntries(mockCMA, entries, { + const result = await createEntriesFromPreview(mockCMA, entries, { spaceId: mockSpaceId, environmentId: mockEnvironmentId, contentTypes: mockContentTypes, @@ -261,7 +261,7 @@ describe('createEntries', () => { }, ]; - const result = await createEntries(mockCMA, entries, { + const result = await createEntriesFromPreview(mockCMA, entries, { spaceId: mockSpaceId, environmentId: mockEnvironmentId, contentTypes: mockContentTypes, @@ -297,7 +297,7 @@ describe('createEntries', () => { }, ]; - const result = await createEntries(mockCMA, entries, { + const result = await createEntriesFromPreview(mockCMA, entries, { spaceId: mockSpaceId, environmentId: mockEnvironmentId, contentTypes: mockContentTypes, @@ -321,7 +321,7 @@ describe('createEntries', () => { }, ]; - const result = await createEntries(mockCMA, entries, { + const result = await createEntriesFromPreview(mockCMA, entries, { spaceId: mockSpaceId, environmentId: mockEnvironmentId, contentTypes: mockContentTypes, @@ -358,7 +358,7 @@ describe('createEntries', () => { }, ]; - const result = await createEntries(mockCMA, entries, { + const result = await createEntriesFromPreview(mockCMA, entries, { spaceId: mockSpaceId, environmentId: mockEnvironmentId, contentTypes: mockContentTypes, @@ -403,7 +403,7 @@ describe('createEntries', () => { }, ]; - const result = await createEntries(mockCMA, entries, { + const result = await createEntriesFromPreview(mockCMA, entries, { spaceId: mockSpaceId, environmentId: mockEnvironmentId, contentTypes: mockContentTypes, diff --git a/apps/google-docs/functions/service/entryService.ts b/apps/google-docs/functions/service/entryService.ts index b9c8c28b9b..5b6b5942f3 100644 --- a/apps/google-docs/functions/service/entryService.ts +++ b/apps/google-docs/functions/service/entryService.ts @@ -568,7 +568,7 @@ function buildUrlToAssetIdMap( return urlToAssetId; } -export async function createEntries( +export async function createEntriesFromPreview( cma: PlainClientAPI, entries: EntryToCreate[], config: { spaceId: string; environmentId: string; contentTypes: ContentTypeProps[] } From a9e8de89e3405f574fe388d8ad4fd9b688354783 Mon Sep 17 00:00:00 2001 From: ryunsong-contentful <124832189+ryunsong-contentful@users.noreply.github.com> Date: Thu, 18 Dec 2025 17:28:18 -0700 Subject: [PATCH 04/14] [INTEG-3359] feat: got google doc picker working (#10357) * feat: got google doc picker working * feat: explicitly add api key for google docs oauth connection --- .../components/page/GettingStartedPage.tsx | 1 + .../src/components/page/OAuthConnector.tsx | 11 ++ .../components/page/SelectDocumentModal.tsx | 166 +++--------------- .../src/hooks/useGoogleDocPicker.tsx | 82 +++++++++ apps/google-docs/src/utils/googleapis.ts | 45 +++++ 5 files changed, 162 insertions(+), 143 deletions(-) create mode 100644 apps/google-docs/src/hooks/useGoogleDocPicker.tsx create mode 100644 apps/google-docs/src/utils/googleapis.ts diff --git a/apps/google-docs/src/components/page/GettingStartedPage.tsx b/apps/google-docs/src/components/page/GettingStartedPage.tsx index bddbc7ac1f..93955fa4a2 100644 --- a/apps/google-docs/src/components/page/GettingStartedPage.tsx +++ b/apps/google-docs/src/components/page/GettingStartedPage.tsx @@ -29,6 +29,7 @@ export const GettingStartedPage = ({ style={{ maxWidth: '900px', margin: `${tokens.spacingL} auto` }}> Google Drive void; isOAuthConnected: boolean; onOauthTokenChange: (token: string) => void; + oauthToken: string; }; export const OAuthConnector = ({ onOAuthConnectedChange, isOAuthConnected, + oauthToken, onOauthTokenChange, }: OAuthConnectorProps) => { const sdk = useSDK(); @@ -25,6 +28,14 @@ export const OAuthConnector = ({ const popupWindowRef = useRef(null); const checkWindowIntervalRef = useRef(null); + const { openPicker, isOpening } = useGoogleDocsPicker(oauthToken, { + onPicked: (files) => { + if (files.length > 0) { + console.log('Picked doc:', files[0]); + } + }, + }); + // Check Google OAuth connection status with polling to handle race conditions const checkGoogleOAuthStatus = async ( expectedStatus?: boolean, diff --git a/apps/google-docs/src/components/page/SelectDocumentModal.tsx b/apps/google-docs/src/components/page/SelectDocumentModal.tsx index d0a3b15a2b..56e8b20070 100644 --- a/apps/google-docs/src/components/page/SelectDocumentModal.tsx +++ b/apps/google-docs/src/components/page/SelectDocumentModal.tsx @@ -1,6 +1,5 @@ -import React, { useEffect, useState } from 'react'; -import { Box, Button, Modal, Note, Radio, Table } from '@contentful/f36-components'; -import tokens from '@contentful/f36-tokens'; +import { useEffect, useRef } from 'react'; +import { useGoogleDocsPicker } from '../../hooks/useGoogleDocPicker'; interface SelectDocumentModalProps { oauthToken: string; @@ -8,155 +7,36 @@ interface SelectDocumentModalProps { onClose: (documentId?: string) => void; } -interface GoogleDoc { - id: string; - name: string; - modifiedTime: string; - owners?: { displayName: string; emailAddress: string }[]; -} - -async function listGoogleDocs(accessToken: string): Promise { - const q = "mimeType='application/vnd.google-apps.document' and trashed=false"; - - const params = new URLSearchParams({ - q, - spaces: 'drive', - pageSize: '50', - fields: 'files(id,name,owners(displayName,emailAddress),modifiedTime)', - }); - - const res = await fetch(`https://www.googleapis.com/drive/v3/files?${params.toString()}`, { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }); - - if (!res.ok) { - const err = await res.text(); - throw new Error(`Drive API error ${res.status}: ${err}`); - } - - const data = await res.json(); - return data.files as Array<{ - id: string; - name: string; - modifiedTime: string; - owners?: { displayName: string; emailAddress: string }[]; - }>; -} - export default function SelectDocumentModal({ oauthToken, isOpen, onClose, }: SelectDocumentModalProps) { - const [googleDocs, setGoogleDocs] = useState([]); - const [selectedDocId, setSelectedDocId] = useState(null); - const [errorMessage, setErrorMessage] = useState(null); - - useEffect(() => { - const fetchGoogleDocs = async () => { - const docs = await listGoogleDocs(oauthToken); - console.log('DOCS', docs); - setGoogleDocs(docs); - }; - fetchGoogleDocs(); - }, [oauthToken]); + const hasOpenedPickerRef = useRef(false); + + const { openPicker } = useGoogleDocsPicker(oauthToken, { + onPicked: (files) => { + if (files.length > 0) { + onClose(files[0].id); + } else { + onClose(); + } + hasOpenedPickerRef.current = false; + }, + }); useEffect(() => { - if (isOpen) { - setSelectedDocId(null); - setErrorMessage(null); + if (isOpen && oauthToken && !hasOpenedPickerRef.current) { + hasOpenedPickerRef.current = true; + openPicker(); } - }, [isOpen]); - - const handleCancel = () => { - onClose(); - }; - const handleNext = () => { - if (!selectedDocId) { - setErrorMessage('Please select a document'); - return; + if (!isOpen) { + hasOpenedPickerRef.current = false; } - setErrorMessage(null); - onClose(selectedDocId); - }; - - const handleRowClick = (docId: string) => { - setSelectedDocId(docId); - setErrorMessage(null); - }; - - return ( - - {() => ( - <> - - - - - - - - Owner - Modified Time - Name - - - - {googleDocs.map((doc) => ( - handleRowClick(doc.id)} - style={{ - cursor: 'pointer', - backgroundColor: selectedDocId === doc.id ? tokens.blue100 : undefined, - }}> - - handleRowClick(doc.id)} - /> - - {doc.owners?.[0]?.displayName ?? 'Unknown'} - {new Date(doc.modifiedTime).toLocaleString()} - {doc.name} - - ))} - -
-
+ }, [isOpen, oauthToken, openPicker]); - {errorMessage && ( - - {errorMessage} - - )} -
- - - - - - )} -
- ); + // This component no longer renders a modal since the Google Picker + // opens as a separate popup. Return null as there's nothing to render. + return null; } diff --git a/apps/google-docs/src/hooks/useGoogleDocPicker.tsx b/apps/google-docs/src/hooks/useGoogleDocPicker.tsx new file mode 100644 index 0000000000..765ad0acae --- /dev/null +++ b/apps/google-docs/src/hooks/useGoogleDocPicker.tsx @@ -0,0 +1,82 @@ +// hooks/useGoogleDocsPicker.ts +import { useCallback, useState } from 'react'; +import { loadGapi, loadPickerApi } from '../utils/googleapis'; + +type PickerCallbackData = { + id: string; + name: string; + mimeType: string; + url?: string; +}; + +type UseGoogleDocsPickerOptions = { + onPicked?: (files: PickerCallbackData[]) => void; +}; + +// These are already exposed by google in the network even if they were hidden as environment variables +// and google acknowledges that these are okay to be public and that restrictions come from defining the +// origin web url that is allowed to use these keys which is defined in a private google docs oauth app. +// Additionally the api key requires a valid OAuth token for operations on private user data. +// That means even if someone sees your key, they cannot: +// 1. access user files without an OAuth token, +// 2. use other APIs you haven’t enabled, + +// Summary: The API keys are defined to only only accept requests from app.contentful.com and ctfapps.net domains. +// See https://developers.google.com/workspace/drive/picker/guides/overview?utm_source=chatgpt.com#create-api-key for more details +const GOOGLE_PICKER_API_KEY = ''; +const GOOGLE_APP_ID = 1; + +export function useGoogleDocsPicker( + accessToken: string | null, + options: UseGoogleDocsPickerOptions = {} +) { + const [isOpening, setIsOpening] = useState(false); + const openPicker = useCallback(async () => { + if (!accessToken) { + console.warn('No Google access token available'); + return; + } + + try { + setIsOpening(true); + await loadGapi(); + await loadPickerApi(); + + const google = (window as any).google; + const gapi = (window as any).gapi; + + // Only show Google Docs + const view = new google.picker.View(google.picker.ViewId.DOCS); + view.setMimeTypes('application/vnd.google-apps.document'); + + const picker = new google.picker.PickerBuilder() + .setOAuthToken(accessToken) + .setDeveloperKey(GOOGLE_PICKER_API_KEY) + .addView(view) + .setOrigin('https://app.contentful.com') + .setCallback((data: any) => { + if (data.action === google.picker.Action.PICKED) { + const docs: PickerCallbackData[] = data.docs.map((doc: any) => ({ + id: doc.id, + name: doc.name, + mimeType: doc.mimeType, + url: doc.url, + })); + options.onPicked?.(docs); + } + }); + + if (GOOGLE_APP_ID) { + picker.setAppId(GOOGLE_APP_ID); + } + + picker.build().setVisible(true); + } catch (e) { + console.error('Error opening Google Docs picker', e); + } finally { + setIsOpening(false); + } + }, [accessToken, options.onPicked]); + + return { openPicker, isOpening }; +} diff --git a/apps/google-docs/src/utils/googleapis.ts b/apps/google-docs/src/utils/googleapis.ts new file mode 100644 index 0000000000..08b1ad5ea8 --- /dev/null +++ b/apps/google-docs/src/utils/googleapis.ts @@ -0,0 +1,45 @@ +// utils/loadGoogleApis.ts +let gapiLoaded: Promise | null = null; +let pickerLoaded: Promise | null = null; + +export function loadGapi(): Promise { + if (gapiLoaded) return gapiLoaded; + gapiLoaded = new Promise((resolve, reject) => { + if ((window as any).gapi) { + resolve(); + return; + } + + const script = document.createElement('script'); + script.src = 'https://apis.google.com/js/api.js'; + script.async = true; + script.onload = () => { + (window as any).gapi.load('client:auth2', () => resolve()); + }; + script.onerror = () => reject(new Error('Failed to load gapi')); + document.body.appendChild(script); + }); + return gapiLoaded; +} + +export function loadPickerApi(): Promise { + if (pickerLoaded) return pickerLoaded; + pickerLoaded = new Promise((resolve, reject) => { + if ((window as any).google && (window as any).google.picker) { + resolve(); + return; + } + + const script = document.createElement('script'); + script.src = 'https://apis.google.com/js/api.js?onload=__pickerOnLoad'; + script.async = true; + + (window as any).__pickerOnLoad = () => { + (window as any).gapi.load('picker', () => resolve()); + }; + + script.onerror = () => reject(new Error('Failed to load Google Picker')); + document.body.appendChild(script); + }); + return pickerLoaded; +} From f660c04d07c4ca6ac9415fa15ce9132572230ba0 Mon Sep 17 00:00:00 2001 From: Adrian Meyer <8539634+primeinteger@users.noreply.github.com> Date: Fri, 19 Dec 2025 14:37:25 -0700 Subject: [PATCH 05/14] feat(google-docs): review entries modal [INTEG-3363] (#10359) --- apps/google-docs/contentful-app-manifest.json | 13 +- .../createEntries/createEntriesHandler.ts | 51 ---- .../page/ContentTypePickerModal.tsx | 14 +- .../src/components/page/ErrorEntriesModal.tsx | 34 +++ .../src/components/page/OAuthConnector.tsx | 7 +- .../components/page/ReviewEntriesModal.tsx | 100 ++++++++ .../src/hooks/useModalManagement.ts | 24 ++ .../src/hooks/useProgressTracking.ts | 11 +- .../src/locations/ConfigScreen.spec.tsx | 10 +- apps/google-docs/src/locations/Page.spec.tsx | 10 +- apps/google-docs/src/locations/Page.tsx | 78 +++--- .../services}/entryService.test.ts | 236 ++++++------------ .../service => src/services}/entryService.ts | 149 ++++++----- apps/google-docs/src/utils/appActionUtils.ts | 16 -- .../service => src}/utils/richtext.ts | 0 apps/google-docs/test/mocks/index.ts | 4 +- apps/google-docs/test/mocks/mockCma.ts | 31 ++- apps/google-docs/test/mocks/mockSdk.ts | 40 ++- 18 files changed, 468 insertions(+), 360 deletions(-) delete mode 100644 apps/google-docs/functions/handlers/createEntries/createEntriesHandler.ts create mode 100644 apps/google-docs/src/components/page/ErrorEntriesModal.tsx create mode 100644 apps/google-docs/src/components/page/ReviewEntriesModal.tsx rename apps/google-docs/{functions/service => src/services}/entryService.test.ts (56%) rename apps/google-docs/{functions/service => src/services}/entryService.ts (85%) rename apps/google-docs/{functions/service => src}/utils/richtext.ts (100%) diff --git a/apps/google-docs/contentful-app-manifest.json b/apps/google-docs/contentful-app-manifest.json index 08a08468e7..eda0208815 100644 --- a/apps/google-docs/contentful-app-manifest.json +++ b/apps/google-docs/contentful-app-manifest.json @@ -50,17 +50,6 @@ "appaction.call" ] }, - { - "id": "createEntries", - "name": "Create Entries", - "description": "Creates entries in Contentful", - "path": "functions/handlers/createEntries/createEntriesHandler.js", - "entryFile": "functions/handlers/createEntries/createEntriesHandler.ts", - "allowNetworks": [], - "accepts": [ - "appaction.call" - ] - }, { "id": "initiateGdocOauth", "name": "Initiate Gdoc OAuth Flow", @@ -115,4 +104,4 @@ } ], "actions": [] -} \ No newline at end of file +} diff --git a/apps/google-docs/functions/handlers/createEntries/createEntriesHandler.ts b/apps/google-docs/functions/handlers/createEntries/createEntriesHandler.ts deleted file mode 100644 index afe1b6020d..0000000000 --- a/apps/google-docs/functions/handlers/createEntries/createEntriesHandler.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { AppActionRequest, FunctionEventHandler } from '@contentful/node-apps-toolkit'; -import { FunctionTypeEnum, FunctionEventContext } from '@contentful/node-apps-toolkit'; -import { EntryToCreate } from '../../agents/documentParserAgent/schema'; -import { createEntriesFromPreview } from '../../service/entryService'; -import { initContentfulManagementClient } from '../../service/initCMAClient'; - -interface CreateEntriesParameters { - entries: EntryToCreate[]; - contentTypeIds: string[]; -} - -export const handler: FunctionEventHandler< - FunctionTypeEnum.AppActionCall, - CreateEntriesParameters -> = async ( - event: AppActionRequest<'Custom', CreateEntriesParameters>, - context: FunctionEventContext -) => { - const { entries, contentTypeIds } = event.body; - - if (!entries || !Array.isArray(entries) || entries.length === 0) { - throw new Error('entries parameter is required and must be a non-empty array'); - } - - if (!contentTypeIds || !Array.isArray(contentTypeIds) || contentTypeIds.length === 0) { - throw new Error('contentTypeIds parameter is required and must be a non-empty array'); - } - - const cma = initContentfulManagementClient(context); - - // Fetch content types - const contentTypesResponse = await cma.contentType.getMany({}); - const contentTypes = contentTypesResponse.items.filter((ct) => - contentTypeIds.includes(ct.sys.id) - ); - - if (contentTypes.length === 0) { - throw new Error('No matching content types found'); - } - - const result = await createEntriesFromPreview(cma, entries, { - spaceId: context.spaceId, - environmentId: context.environmentId, - contentTypes: contentTypes, - }); - - return { - success: true, - result: result, - }; -}; diff --git a/apps/google-docs/src/components/page/ContentTypePickerModal.tsx b/apps/google-docs/src/components/page/ContentTypePickerModal.tsx index b3355e5a5e..451626cde4 100644 --- a/apps/google-docs/src/components/page/ContentTypePickerModal.tsx +++ b/apps/google-docs/src/components/page/ContentTypePickerModal.tsx @@ -41,6 +41,7 @@ export const ContentTypePickerModal = ({ const [contentTypes, setContentTypes] = useState([]); const [isLoading, setIsLoading] = useState(false); const [hasAttemptedSubmit, setHasAttemptedSubmit] = useState(false); + const [hasFetchError, setHasFetchError] = useState(false); const isInvalidSelection = useMemo( () => selectedContentTypes.length === 0, @@ -52,11 +53,14 @@ export const ContentTypePickerModal = ({ [isInvalidSelection, hasAttemptedSubmit] ); + const showFetchError = hasFetchError && !isLoading; + useEffect(() => { // Fetch content types when component mounts const fetchContentTypes = async () => { try { setIsLoading(true); + setHasFetchError(false); const space = await sdk.cma.space.get({}); const environment = await sdk.cma.environment.get({ spaceId: space.sys.id }); const contentTypesResponse = await sdk.cma.contentType.getMany({ @@ -66,7 +70,8 @@ export const ContentTypePickerModal = ({ setContentTypes(contentTypesResponse.items || []); } catch (error) { console.error('Failed to fetch content types:', error); - sdk.notifier.error('Failed to load content types'); + setHasFetchError(true); + setContentTypes([]); } finally { setIsLoading(false); } @@ -130,7 +135,7 @@ export const ContentTypePickerModal = ({ Select the content type(s) you would like to use with this sync. - + Content type + {showFetchError && ( + + Unable to load content types. + + )} {isInvalidSelectionError && ( You must select at least one content type. diff --git a/apps/google-docs/src/components/page/ErrorEntriesModal.tsx b/apps/google-docs/src/components/page/ErrorEntriesModal.tsx new file mode 100644 index 0000000000..d793f02d3a --- /dev/null +++ b/apps/google-docs/src/components/page/ErrorEntriesModal.tsx @@ -0,0 +1,34 @@ +import { Button, Modal, Paragraph } from '@contentful/f36-components'; + +interface ErrorEntriesModalProps { + isOpen: boolean; + onClose: () => void; + onTryAgain: () => void; +} + +export const ErrorEntriesModal: React.FC = ({ + isOpen, + onClose, + onTryAgain, +}) => { + return ( + + {() => ( + <> + + + No entries were created, please try again. + + + + + + + )} + + ); +}; diff --git a/apps/google-docs/src/components/page/OAuthConnector.tsx b/apps/google-docs/src/components/page/OAuthConnector.tsx index ced7c2f7b7..053c3e61f0 100644 --- a/apps/google-docs/src/components/page/OAuthConnector.tsx +++ b/apps/google-docs/src/components/page/OAuthConnector.tsx @@ -145,8 +145,6 @@ export const OAuthConnector = ({ ); // Check the updated status after OAuth completion - expect it to be connected await checkGoogleOAuthStatus(true); - - sdk.notifier.success('OAuth complete'); cleanup(); setIsOAuthLoading(false); } @@ -202,7 +200,7 @@ export const OAuthConnector = ({ } catch (error) { cleanup(); setIsOAuthLoading(false); - sdk.notifier.error('Failed to initiate OAuth flow'); + sdk.notifier.error('Unable to connect to Google Drive. Please try again.'); } }; @@ -228,9 +226,8 @@ export const OAuthConnector = ({ await checkGoogleOAuthStatus(false); setIsHoveringConnected(false); - sdk.notifier.success('Disconnected from Google OAuth'); } catch (error) { - sdk.notifier.error('Failed to disconnect from Google OAuth'); + sdk.notifier.error('Unable to disconnect from Google Drive. Please try again.'); } finally { setIsDisconnecting(false); } diff --git a/apps/google-docs/src/components/page/ReviewEntriesModal.tsx b/apps/google-docs/src/components/page/ReviewEntriesModal.tsx new file mode 100644 index 0000000000..06c880c581 --- /dev/null +++ b/apps/google-docs/src/components/page/ReviewEntriesModal.tsx @@ -0,0 +1,100 @@ +import React, { useMemo } from 'react'; +import { Button, Modal, Paragraph, TextLink, Flex, Box } from '@contentful/f36-components'; +import { EntryProps } from 'contentful-management'; +import { ArrowSquareOutIcon } from '@contentful/f36-icons'; +import tokens from '@contentful/f36-tokens'; + +interface ReviewEntriesModalProps { + isOpen: boolean; + onClose: () => void; + createdEntries: EntryProps[]; + spaceId: string; + defaultLocale: string; +} + +function getEntryDisplayName(entry: EntryProps, defaultLocale: string): string { + // Try to find a 'title' field first + if (entry.fields.title) { + const titleValue = entry.fields.title[defaultLocale] || Object.values(entry.fields.title)[0]; + if (titleValue && typeof titleValue === 'string') { + return titleValue; + } + } + + // Fall back to the first text/Symbol field + for (const [_fieldId, localizedValue] of Object.entries(entry.fields)) { + if (localizedValue && typeof localizedValue === 'object') { + const value = localizedValue[defaultLocale] || Object.values(localizedValue)[0]; + if (value && typeof value === 'string' && value.trim().length > 0) { + return value; + } + } + } + + // Last resort: use entry ID + return entry.sys.id; +} + +export const ReviewEntriesModal: React.FC = ({ + isOpen, + onClose, + createdEntries, + spaceId, + defaultLocale, +}) => { + const entryLinks = useMemo(() => { + return createdEntries.map((entry) => { + const displayName = getEntryDisplayName(entry, defaultLocale); + const url = `https://app.contentful.com/spaces/${spaceId}/entries/${entry.sys.id}`; + + return { + entry, + displayName, + url, + }; + }); + }, [createdEntries, spaceId, defaultLocale]); + + const entryCount = createdEntries.length; + const entryHasText = entryCount === 1 ? 'entry has' : 'entries have'; + + return ( + + {() => ( + <> + + + + + Success! {entryCount} {entryHasText} been created: + + + {entryLinks.map(({ entry, displayName, url }) => ( + + } + alignIcon="end"> + {displayName} + + + ))} + + + + + + + + )} + + ); +}; diff --git a/apps/google-docs/src/hooks/useModalManagement.ts b/apps/google-docs/src/hooks/useModalManagement.ts index a06b0d95a8..1430bb6a29 100644 --- a/apps/google-docs/src/hooks/useModalManagement.ts +++ b/apps/google-docs/src/hooks/useModalManagement.ts @@ -5,6 +5,8 @@ interface ModalStates { isContentTypePickerOpen: boolean; isConfirmCancelModalOpen: boolean; isPreviewModalOpen: boolean; + isReviewModalOpen: boolean; + isErrorEntriesModalOpen: boolean; } interface ModalSetters { @@ -12,6 +14,8 @@ interface ModalSetters { setIsContentTypePickerOpen: (value: boolean) => void; setIsConfirmCancelModalOpen: (value: boolean) => void; setIsPreviewModalOpen: (value: boolean) => void; + setIsReviewModalOpen: (value: boolean) => void; + setIsErrorEntriesModalOpen: (value: boolean) => void; } export enum ModalType { @@ -19,6 +23,8 @@ export enum ModalType { CONTENT_TYPE_PICKER = 'contentTypePicker', CONFIRM_CANCEL = 'confirmCancel', PREVIEW = 'preview', + REVIEW = 'review', + ERROR_ENTRIES = 'errorEntries', } export const useModalManagement = () => { @@ -26,6 +32,8 @@ export const useModalManagement = () => { const [isContentTypePickerOpen, setIsContentTypePickerOpen] = useState(false); const [isConfirmCancelModalOpen, setIsConfirmCancelModalOpen] = useState(false); const [isPreviewModalOpen, setIsPreviewModalOpen] = useState(false); + const [isReviewModalOpen, setIsReviewModalOpen] = useState(false); + const [isErrorEntriesModalOpen, setIsErrorEntriesModalOpen] = useState(false); const openModal = (modalType: ModalType) => { switch (modalType) { @@ -41,6 +49,12 @@ export const useModalManagement = () => { case ModalType.PREVIEW: setIsPreviewModalOpen(true); break; + case ModalType.REVIEW: + setIsReviewModalOpen(true); + break; + case ModalType.ERROR_ENTRIES: + setIsErrorEntriesModalOpen(true); + break; } }; @@ -58,6 +72,12 @@ export const useModalManagement = () => { case ModalType.PREVIEW: setIsPreviewModalOpen(false); break; + case ModalType.REVIEW: + setIsReviewModalOpen(false); + break; + case ModalType.ERROR_ENTRIES: + setIsErrorEntriesModalOpen(false); + break; } }; @@ -67,12 +87,16 @@ export const useModalManagement = () => { isContentTypePickerOpen, isConfirmCancelModalOpen, isPreviewModalOpen, + isReviewModalOpen, + isErrorEntriesModalOpen, } as ModalStates, setModalStates: { setIsUploadModalOpen, setIsContentTypePickerOpen, setIsConfirmCancelModalOpen, setIsPreviewModalOpen, + setIsReviewModalOpen, + setIsErrorEntriesModalOpen, } as ModalSetters, openModal, closeModal, diff --git a/apps/google-docs/src/hooks/useProgressTracking.ts b/apps/google-docs/src/hooks/useProgressTracking.ts index e1dfeb60b8..0b7354ffb1 100644 --- a/apps/google-docs/src/hooks/useProgressTracking.ts +++ b/apps/google-docs/src/hooks/useProgressTracking.ts @@ -1,26 +1,19 @@ -import { useState, useMemo, useCallback } from 'react'; +import { useState, useCallback } from 'react'; import { SelectedContentType } from '../components/page/ContentTypePickerModal'; export const useProgressTracking = () => { - const [hasStarted, setHasStarted] = useState(false); const [documentId, setDocumentId] = useState(''); const [selectedContentTypes, setSelectedContentTypes] = useState([]); const [pendingCloseAction, setPendingCloseAction] = useState<(() => void) | null>(null); - const hasProgress = useMemo( - () => hasStarted && documentId.trim().length > 0, - [hasStarted, documentId] - ); + const hasProgress = documentId.trim().length > 0; const resetProgress = useCallback(() => { - setHasStarted(false); setDocumentId(''); setSelectedContentTypes([]); }, []); return { - hasStarted, - setHasStarted, documentId, setDocumentId, selectedContentTypes, diff --git a/apps/google-docs/src/locations/ConfigScreen.spec.tsx b/apps/google-docs/src/locations/ConfigScreen.spec.tsx index 1de44078e0..d8bc97ffdc 100644 --- a/apps/google-docs/src/locations/ConfigScreen.spec.tsx +++ b/apps/google-docs/src/locations/ConfigScreen.spec.tsx @@ -12,9 +12,11 @@ describe('Config Screen component', () => { it('Component text exists', async () => { const { getByText } = render(); - // simulate the user clicking the install button - await mockSdk.app.onConfigure.mock.calls[0][0](); - - expect(getByText('Welcome to your contentful app. This is your config page.')).toBeTruthy(); + expect(getByText('Set up Google Drive app')).toBeTruthy(); + expect( + getByText( + 'Connect Google Drive to Contentful to seamlessly connect content, eliminating copy-paste, reducing errors, and speeding up your publishing workflow.' + ) + ).toBeTruthy(); }); }); diff --git a/apps/google-docs/src/locations/Page.spec.tsx b/apps/google-docs/src/locations/Page.spec.tsx index c849348f44..7422863fc4 100644 --- a/apps/google-docs/src/locations/Page.spec.tsx +++ b/apps/google-docs/src/locations/Page.spec.tsx @@ -1,5 +1,5 @@ import Page from './Page'; -import { render } from '@testing-library/react'; +import { render, waitFor } from '@testing-library/react'; import { mockCma, mockSdk } from '../../test/mocks'; import { vi } from 'vitest'; @@ -9,9 +9,11 @@ vi.mock('@contentful/react-apps-toolkit', () => ({ })); describe('Page component', () => { - it('Component text exists', () => { - const { getByText } = render(); + it('renders the Google Drive heading', async () => { + const { getByRole } = render(); - expect(getByText('Document Uploader')).toBeTruthy(); + await waitFor(() => { + expect(getByRole('heading', { name: 'Google Drive' })).toBeTruthy(); + }); }); }); diff --git a/apps/google-docs/src/locations/Page.tsx b/apps/google-docs/src/locations/Page.tsx index 7128b8da98..da3abe92a9 100644 --- a/apps/google-docs/src/locations/Page.tsx +++ b/apps/google-docs/src/locations/Page.tsx @@ -1,5 +1,4 @@ import { useEffect, useRef, useState } from 'react'; -import { Box, Button, Card, Heading, Layout, Note } from '@contentful/f36-components'; import { PageAppSDK } from '@contentful/app-sdk'; import { useSDK } from '@contentful/react-apps-toolkit'; import { GettingStartedPage } from '../components/page/GettingStartedPage'; @@ -13,16 +12,17 @@ import { useProgressTracking } from '../hooks/useProgressTracking'; import { useDocumentSubmission } from '../hooks/useDocumentSubmission'; import SelectDocumentModal from '../components/page/SelectDocumentModal'; import { ViewPreviewModal } from '../components/page/ViewPreviewModal'; -import { createEntriesAction } from '../utils/appActionUtils'; +import { ReviewEntriesModal } from '../components/page/ReviewEntriesModal'; +import { ErrorEntriesModal } from '../components/page/ErrorEntriesModal'; +import { createEntriesFromPreview, EntryCreationResult } from '../services/entryService'; const Page = () => { const sdk = useSDK(); const { modalStates, openModal, closeModal } = useModalManagement(); const [oauthToken, setOauthToken] = useState(''); const [isCreatingEntries, setIsCreatingEntries] = useState(false); + const [createdEntries, setCreatedEntries] = useState([]); const { - hasStarted, - setHasStarted, documentId, setDocumentId, selectedContentTypes, @@ -46,7 +46,6 @@ const Page = () => { }; const handleGetStarted = () => { - setHasStarted(true); openModal(ModalType.UPLOAD); }; @@ -76,7 +75,6 @@ const Page = () => { } else { // No progress, reset to getting started page closeModal(ModalType.UPLOAD); - setHasStarted(false); } }; @@ -109,13 +107,7 @@ const Page = () => { }; const handleContentTypeSelected = async (contentTypes: SelectedContentType[]) => { - const names = contentTypes.map((ct) => ct.name).join(', '); const ids = contentTypes.map((ct) => ct.id); - - sdk.notifier.success( - `Selected ${contentTypes.length} content type${contentTypes.length > 1 ? 's' : ''}: ${names}` - ); - await submit(ids); }; @@ -128,29 +120,47 @@ const Page = () => { setIsCreatingEntries(true); try { const ids = contentTypes.map((ct) => ct.id); - const entryResult: any = await createEntriesAction(sdk, previewEntries, ids); + const entryResult: EntryCreationResult = await createEntriesFromPreview( + sdk, + previewEntries, + ids + ); + + const createdCount = entryResult.createdEntries.length; + const errorCount = entryResult.errors.length; + + closeModal(ModalType.PREVIEW); - if (entryResult.errorCount > 0) { - sdk.notifier.warning( - `Created ${entryResult.createdCount} entries with ${entryResult.errorCount} errors` - ); + if (createdCount === 0) { console.error('Entry creation errors:', entryResult.errors); - } else { - sdk.notifier.success(`Successfully created ${entryResult.createdCount} entries`); + openModal(ModalType.ERROR_ENTRIES); + return; } - // Close the preview modal and reset progress after creating entries - closeModal(ModalType.PREVIEW); + setCreatedEntries(entryResult.createdEntries); resetProgress(); + openModal(ModalType.REVIEW); } catch (error) { + closeModal(ModalType.PREVIEW); const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; - sdk.notifier.error(`Failed to create entries: ${errorMessage}`); console.error('Entry creation failed:', error); + openModal(ModalType.ERROR_ENTRIES); } finally { setIsCreatingEntries(false); } }; + const handleErrorModalTryAgain = () => { + closeModal(ModalType.ERROR_ENTRIES); + // Reopen the preview modal so user can try again + openModal(ModalType.PREVIEW); + }; + + const handleErrorModalCancel = () => { + closeModal(ModalType.ERROR_ENTRIES); + resetProgress(); + }; + // Close the ContentTypePickerModal when submission completes and open preview modal useEffect(() => { const submissionJustCompleted = prevIsSubmittingRef.current && !isSubmitting; @@ -169,19 +179,13 @@ const Page = () => { prevIsSubmittingRef.current = isSubmitting; }, [isSubmitting, modalStates.isContentTypePickerOpen, closeModal, openModal, previewEntries]); - // Show getting started page if not started yet - if (!hasStarted) { - return ( + return ( + <> - ); - } - - return ( - <> { onConfirm={() => handlePreviewModalConfirm(selectedContentTypes)} isSubmitting={isCreatingEntries} /> + + closeModal(ModalType.REVIEW)} + createdEntries={createdEntries} + spaceId={sdk.ids.space} + defaultLocale={sdk.locales.default} + /> + + ); }; diff --git a/apps/google-docs/functions/service/entryService.test.ts b/apps/google-docs/src/services/entryService.test.ts similarity index 56% rename from apps/google-docs/functions/service/entryService.test.ts rename to apps/google-docs/src/services/entryService.test.ts index 89556524d3..29b19452df 100644 --- a/apps/google-docs/functions/service/entryService.test.ts +++ b/apps/google-docs/src/services/entryService.test.ts @@ -1,27 +1,12 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import type { PlainClientAPI, EntryProps, ContentTypeProps } from 'contentful-management'; -import { createEntriesFromPreview, EntryCreationResult } from './entryService'; -import { EntryToCreate } from '../agents/documentParserAgent/schema'; - -// Mock CMA client -const createMockCMA = (): PlainClientAPI => { - return { - asset: { - create: vi.fn(), - processForAllLocales: vi.fn(), - get: vi.fn(), - publish: vi.fn(), - }, - entry: { - create: vi.fn(), - }, - } as unknown as PlainClientAPI; -}; - -describe('createEntries', () => { - let mockCMA: PlainClientAPI; - const mockSpaceId = 'test-space-id'; - const mockEnvironmentId = 'test-environment-id'; +import type { PageAppSDK } from '@contentful/app-sdk'; +import type { EntryProps, ContentTypeProps } from 'contentful-management'; +import { createEntriesFromPreview } from './entryService'; +import { EntryToCreate } from '../../functions/agents/documentParserAgent/schema'; +import { createMockSDK } from '../../test/mocks'; + +describe('createEntriesFromPreview', () => { + let mockSDK: PageAppSDK; const mockContentTypes: ContentTypeProps[] = [ { sys: { id: 'blogPost', type: 'ContentType' }, @@ -34,41 +19,34 @@ describe('createEntries', () => { ]; beforeEach(() => { - mockCMA = createMockCMA(); + mockSDK = createMockSDK(); vi.clearAllMocks(); + // Mock contentType.getMany to return our mock content types + vi.mocked(mockSDK.cma.contentType.getMany).mockResolvedValue({ + items: mockContentTypes, + total: mockContentTypes.length, + } as any); }); describe('Input Validation', () => { - it('should reject null CMA client', async () => { - const result = await createEntriesFromPreview(null as any, [], { - spaceId: mockSpaceId, - environmentId: mockEnvironmentId, - contentTypes: mockContentTypes, - }); + it('should reject null SDK', async () => { + const result = await createEntriesFromPreview(null as any, [], ['blogPost']); expect(result.createdEntries).toHaveLength(0); expect(result.errors).toHaveLength(1); - expect(result.errors[0].error).toContain('CMA client is required'); + expect(result.errors[0].error).toContain('SDK is required'); }); - it('should reject undefined CMA client', async () => { - const result = await createEntriesFromPreview(undefined as any, [], { - spaceId: mockSpaceId, - environmentId: mockEnvironmentId, - contentTypes: mockContentTypes, - }); + it('should reject undefined SDK', async () => { + const result = await createEntriesFromPreview(undefined as any, [], ['blogPost']); expect(result.createdEntries).toHaveLength(0); expect(result.errors).toHaveLength(1); - expect(result.errors[0].error).toContain('CMA client is required'); + expect(result.errors[0].error).toContain('SDK is required'); }); it('should reject null entries array', async () => { - const result = await createEntriesFromPreview(mockCMA, null as any, { - spaceId: mockSpaceId, - environmentId: mockEnvironmentId, - contentTypes: mockContentTypes, - }); + const result = await createEntriesFromPreview(mockSDK, null as any, ['blogPost']); expect(result.createdEntries).toHaveLength(0); expect(result.errors).toHaveLength(1); @@ -76,11 +54,7 @@ describe('createEntries', () => { }); it('should reject undefined entries array', async () => { - const result = await createEntriesFromPreview(mockCMA, undefined as any, { - spaceId: mockSpaceId, - environmentId: mockEnvironmentId, - contentTypes: mockContentTypes, - }); + const result = await createEntriesFromPreview(mockSDK, undefined as any, ['blogPost']); expect(result.createdEntries).toHaveLength(0); expect(result.errors).toHaveLength(1); @@ -88,30 +62,22 @@ describe('createEntries', () => { }); it('should reject empty entries array', async () => { - const result = await createEntriesFromPreview(mockCMA, [], { - spaceId: mockSpaceId, - environmentId: mockEnvironmentId, - contentTypes: mockContentTypes, - }); + const result = await createEntriesFromPreview(mockSDK, [], ['blogPost']); expect(result.createdEntries).toHaveLength(0); expect(result.errors).toHaveLength(1); - expect(result.errors[0].error).toContain('Entries array cannot be empty'); + expect(result.errors[0].error).toContain('Entries cannot be empty'); }); it('should reject non-array entries', async () => { - const result = await createEntriesFromPreview(mockCMA, {} as any, { - spaceId: mockSpaceId, - environmentId: mockEnvironmentId, - contentTypes: mockContentTypes, - }); + const result = await createEntriesFromPreview(mockSDK, {} as any, ['blogPost']); expect(result.createdEntries).toHaveLength(0); expect(result.errors).toHaveLength(1); expect(result.errors[0].error).toContain('Entries must be an array'); }); - it('should reject null config', async () => { + it('should reject null contentTypeIds', async () => { const entries: EntryToCreate[] = [ { contentTypeId: 'blogPost', @@ -119,14 +85,14 @@ describe('createEntries', () => { }, ]; - const result = await createEntriesFromPreview(mockCMA, entries, null as any); + const result = await createEntriesFromPreview(mockSDK, entries, null as any); expect(result.createdEntries).toHaveLength(0); expect(result.errors).toHaveLength(1); - expect(result.errors[0].error).toContain('Config is required'); + expect(result.errors[0].error).toContain('contentTypeIds'); }); - it('should reject empty spaceId', async () => { + it('should reject empty contentTypeIds array', async () => { const entries: EntryToCreate[] = [ { contentTypeId: 'blogPost', @@ -134,110 +100,76 @@ describe('createEntries', () => { }, ]; - const result = await createEntriesFromPreview(mockCMA, entries, { - spaceId: '', - environmentId: mockEnvironmentId, - contentTypes: mockContentTypes, - }); + const result = await createEntriesFromPreview(mockSDK, entries, []); expect(result.createdEntries).toHaveLength(0); expect(result.errors).toHaveLength(1); - expect(result.errors[0].error).toContain('spaceId'); + expect(result.errors[0].error).toContain('contentTypeIds cannot be empty'); }); - it('should reject empty environmentId', async () => { + it('should reject entry with null contentTypeId', async () => { const entries: EntryToCreate[] = [ { - contentTypeId: 'blogPost', + contentTypeId: null as any, fields: { title: { 'en-US': 'Test' } }, }, ]; - const result = await createEntriesFromPreview(mockCMA, entries, { - spaceId: mockSpaceId, - environmentId: '', - contentTypes: mockContentTypes, - }); + const result = await createEntriesFromPreview(mockSDK, entries, ['blogPost']); expect(result.createdEntries).toHaveLength(0); expect(result.errors).toHaveLength(1); - expect(result.errors[0].error).toContain('environmentId'); + expect(result.errors[0].error).toContain('contentTypeId'); }); - it('should reject null contentTypes', async () => { + it('should reject entry with null fields', async () => { const entries: EntryToCreate[] = [ { contentTypeId: 'blogPost', - fields: { title: { 'en-US': 'Test' } }, - }, - ]; - - const result = await createEntriesFromPreview(mockCMA, entries, { - spaceId: mockSpaceId, - environmentId: mockEnvironmentId, - contentTypes: null as any, - }); - - expect(result.createdEntries).toHaveLength(0); - expect(result.errors).toHaveLength(1); - expect(result.errors[0].error).toContain('contentTypes'); - }); - - it('should reject entry with null contentTypeId', async () => { - const entries: EntryToCreate[] = [ - { - contentTypeId: null as any, - fields: { title: { 'en-US': 'Test' } }, + fields: null as any, }, ]; - const result = await createEntriesFromPreview(mockCMA, entries, { - spaceId: mockSpaceId, - environmentId: mockEnvironmentId, - contentTypes: mockContentTypes, - }); + const result = await createEntriesFromPreview(mockSDK, entries, ['blogPost']); expect(result.createdEntries).toHaveLength(0); expect(result.errors).toHaveLength(1); - expect(result.errors[0].error).toContain('contentTypeId'); + expect(result.errors[0].error).toContain('fields'); }); - it('should reject entry with null fields', async () => { + it('should reject entry with null field value', async () => { const entries: EntryToCreate[] = [ { contentTypeId: 'blogPost', - fields: null as any, + fields: { title: { 'en-US': null as any } }, }, ]; - const result = await createEntriesFromPreview(mockCMA, entries, { - spaceId: mockSpaceId, - environmentId: mockEnvironmentId, - contentTypes: mockContentTypes, - }); + const result = await createEntriesFromPreview(mockSDK, entries, ['blogPost']); expect(result.createdEntries).toHaveLength(0); expect(result.errors).toHaveLength(1); - expect(result.errors[0].error).toContain('fields'); + expect(result.errors[0].error).toContain('null value'); }); - it('should reject entry with null field value', async () => { + it('should handle no matching content types', async () => { + vi.mocked(mockSDK.cma.contentType.getMany).mockResolvedValue({ + items: [], + total: 0, + } as any); + const entries: EntryToCreate[] = [ { contentTypeId: 'blogPost', - fields: { title: { 'en-US': null as any } }, + fields: { title: { 'en-US': 'Test' } }, }, ]; - const result = await createEntriesFromPreview(mockCMA, entries, { - spaceId: mockSpaceId, - environmentId: mockEnvironmentId, - contentTypes: mockContentTypes, - }); + const result = await createEntriesFromPreview(mockSDK, entries, ['blogPost']); expect(result.createdEntries).toHaveLength(0); expect(result.errors).toHaveLength(1); - expect(result.errors[0].error).toContain('null value'); + expect(result.errors[0].error).toContain('No matching content types found'); }); }); @@ -252,7 +184,7 @@ describe('createEntries', () => { fields: { title: { 'en-US': 'Test Title' } }, } as unknown as EntryProps; - vi.mocked(mockCMA.entry.create).mockResolvedValue(mockEntry); + vi.mocked(mockSDK.cma.entry.create).mockResolvedValue(mockEntry); const entries: EntryToCreate[] = [ { @@ -261,16 +193,16 @@ describe('createEntries', () => { }, ]; - const result = await createEntriesFromPreview(mockCMA, entries, { - spaceId: mockSpaceId, - environmentId: mockEnvironmentId, - contentTypes: mockContentTypes, - }); + const result = await createEntriesFromPreview(mockSDK, entries, ['blogPost']); expect(result.createdEntries).toHaveLength(1); expect(result.errors).toHaveLength(0); - expect(mockCMA.entry.create).toHaveBeenCalledWith( - { spaceId: mockSpaceId, environmentId: mockEnvironmentId, contentTypeId: 'blogPost' }, + expect(mockSDK.cma.entry.create).toHaveBeenCalledWith( + { + spaceId: mockSDK.ids.space, + environmentId: mockSDK.ids.environment, + contentTypeId: 'blogPost', + }, { fields: { title: { 'en-US': 'Test Title' } } } ); }); @@ -285,7 +217,7 @@ describe('createEntries', () => { fields: { content: { 'en-US': { nodeType: 'document', content: [] } } }, } as unknown as EntryProps; - vi.mocked(mockCMA.entry.create).mockResolvedValue(mockEntry); + vi.mocked(mockSDK.cma.entry.create).mockResolvedValue(mockEntry); const entries: EntryToCreate[] = [ { @@ -297,22 +229,18 @@ describe('createEntries', () => { }, ]; - const result = await createEntriesFromPreview(mockCMA, entries, { - spaceId: mockSpaceId, - environmentId: mockEnvironmentId, - contentTypes: mockContentTypes, - }); + const result = await createEntriesFromPreview(mockSDK, entries, ['blogPost']); expect(result.createdEntries).toHaveLength(1); expect(result.errors).toHaveLength(0); - expect(mockCMA.entry.create).toHaveBeenCalled(); + expect(mockSDK.cma.entry.create).toHaveBeenCalled(); }); }); describe('Error Handling', () => { it('should handle CMA API errors gracefully', async () => { const error = new Error('CMA API Error'); - vi.mocked(mockCMA.entry.create).mockRejectedValue(error); + vi.mocked(mockSDK.cma.entry.create).mockRejectedValue(error); const entries: EntryToCreate[] = [ { @@ -321,11 +249,7 @@ describe('createEntries', () => { }, ]; - const result = await createEntriesFromPreview(mockCMA, entries, { - spaceId: mockSpaceId, - environmentId: mockEnvironmentId, - contentTypes: mockContentTypes, - }); + const result = await createEntriesFromPreview(mockSDK, entries, ['blogPost']); expect(result.createdEntries).toHaveLength(0); expect(result.errors).toHaveLength(1); @@ -340,10 +264,10 @@ describe('createEntries', () => { type: 'Entry', contentType: { sys: { id: 'blogPost', type: 'Link', linkType: 'ContentType' } }, }, - fields: { title: { 'en-US': 'Test Title 1' } }, + fields: { title: { 'en-US': 'Test Title 2' } }, } as unknown as EntryProps; - vi.mocked(mockCMA.entry.create) + vi.mocked(mockSDK.cma.entry.create) .mockRejectedValueOnce(new Error('First entry failed')) .mockResolvedValueOnce(mockEntry1); @@ -358,11 +282,7 @@ describe('createEntries', () => { }, ]; - const result = await createEntriesFromPreview(mockCMA, entries, { - spaceId: mockSpaceId, - environmentId: mockEnvironmentId, - contentTypes: mockContentTypes, - }); + const result = await createEntriesFromPreview(mockSDK, entries, ['blogPost']); expect(result.createdEntries).toHaveLength(1); expect(result.errors).toHaveLength(1); @@ -386,11 +306,9 @@ describe('createEntries', () => { fields: {}, } as EntryProps; - vi.mocked(mockCMA.asset.create).mockResolvedValue(mockAsset); - vi.mocked(mockCMA.asset.processForAllLocales).mockResolvedValue(mockAsset); - vi.mocked(mockCMA.asset.get).mockResolvedValue(mockAsset); - vi.mocked(mockCMA.asset.publish).mockResolvedValue(mockAsset); - vi.mocked(mockCMA.entry.create).mockResolvedValue(mockEntry); + vi.mocked(mockSDK.cma.asset.create).mockResolvedValue(mockAsset); + vi.mocked(mockSDK.cma.asset.processForAllLocales).mockResolvedValue(mockAsset); + vi.mocked(mockSDK.cma.entry.create).mockResolvedValue(mockEntry); const entries: EntryToCreate[] = [ { @@ -403,17 +321,15 @@ describe('createEntries', () => { }, ]; - const result = await createEntriesFromPreview(mockCMA, entries, { - spaceId: mockSpaceId, - environmentId: mockEnvironmentId, - contentTypes: mockContentTypes, - }); + const result = await createEntriesFromPreview(mockSDK, entries, ['blogPost']); - expect(mockCMA.asset.create).toHaveBeenCalled(); - const assetCreateCall = vi.mocked(mockCMA.asset.create).mock.calls[0]; + expect(mockSDK.cma.asset.create).toHaveBeenCalled(); + const assetCreateCall = vi.mocked(mockSDK.cma.asset.create).mock.calls[0]; expect(assetCreateCall[1].fields.title['en-US']).toBe('Alt text'); expect(assetCreateCall[1].fields.file['en-US'].contentType).toBe('image/png'); expect(assetCreateCall[1].fields.file['en-US'].fileName).toContain('.png'); + expect(result.createdEntries).toHaveLength(1); + expect(result.errors).toHaveLength(0); }); }); }); diff --git a/apps/google-docs/functions/service/entryService.ts b/apps/google-docs/src/services/entryService.ts similarity index 85% rename from apps/google-docs/functions/service/entryService.ts rename to apps/google-docs/src/services/entryService.ts index 5b6b5942f3..d4e4e8ff86 100644 --- a/apps/google-docs/functions/service/entryService.ts +++ b/apps/google-docs/src/services/entryService.ts @@ -1,12 +1,13 @@ -import { PlainClientAPI, EntryProps, ContentTypeProps } from 'contentful-management'; -import { extname } from 'path'; -import { EntryToCreate } from '../agents/documentParserAgent/schema'; -import { MarkdownParser } from './utils/richtext'; +import { PageAppSDK, ConfigAppSDK } from '@contentful/app-sdk'; +import { EntryProps, ContentTypeProps } from 'contentful-management'; +import { EntryToCreate } from '../../functions/agents/documentParserAgent/schema'; +import { MarkdownParser } from '../utils/richtext'; + /** - * INTEG-3264: Service for creating entries in Contentful using the Contentful Management API + * Service for creating entries in Contentful using the Contentful Management API * * This service takes the output from the Document Parser Agent (which extracts entries from documents) - * and creates them in Contentful using the CMA client. + * and creates them in Contentful using the CMA client from the SDK. */ export interface EntryCreationResult { @@ -21,8 +22,6 @@ export interface EntryCreationResult { // Precompiled regex for markdown image tokens: ![alt](url) // Note: This regex captures both alt text and URL for metadata extraction const IMAGE_TOKEN_REGEX = /!\[([^\]]*?)\]\(([\s\S]*?)\)/g; -// Regex for extracting just URLs (for backward compatibility) -const IMAGE_URL_REGEX = /!\[[^\]]*?\]\(([\s\S]*?)\)/g; /** * MIME type mapping for common file extensions @@ -72,6 +71,14 @@ function isValidUrl(url: string): boolean { } } +function getFileExtension(pathname: string): string { + const lastDot = pathname.lastIndexOf('.'); + if (lastDot === -1 || lastDot === pathname.length - 1) { + return ''; + } + return pathname.slice(lastDot + 1).toLowerCase(); +} + /** * Extracts image metadata from markdown image tokens * Returns array of ImageMetadata objects @@ -103,8 +110,7 @@ function extractImageMetadata(markdownText: string): ImageMetadata[] { const urlObj = new URL(url); const pathname = urlObj.pathname.toLowerCase(); - // Extract extension using Node's path module - const extension = extname(pathname).slice(1).toLowerCase(); + const extension = getFileExtension(pathname); fileExtension = extension || 'jpg'; contentType = MIME_TYPES[fileExtension] || 'image/jpeg'; @@ -229,10 +235,11 @@ async function retryWithBackoff( * The asset will be processed and published by Contentful in the background. */ async function createAssetFromUrlFast( - cma: PlainClientAPI, + cma: PageAppSDK['cma'] | ConfigAppSDK['cma'], spaceId: string, environmentId: string, url: string, + defaultLocale: string, metadata?: { title?: string; altText?: string; fileName?: string; contentType?: string } ) { // Validate inputs @@ -261,9 +268,9 @@ async function createAssetFromUrlFast( { spaceId, environmentId }, { fields: { - title: { 'en-US': title }, + title: { [defaultLocale]: title }, file: { - 'en-US': { + [defaultLocale]: { contentType, fileName, upload: validatedUrl, @@ -341,10 +348,6 @@ function transformFieldsForContentType( return transformed; } -/** - * Validates input parameters for createEntries function - * @throws Error if validation fails - */ /** * Validates that a value is a non-empty string */ @@ -376,15 +379,15 @@ function validateObject(value: unknown, name: string): asserts value is Record(entries, 'Entries array'); - - // Validate config - if (!config) { - throw new Error('Config is required and cannot be null or undefined'); - } - const { spaceId, environmentId, contentTypes } = config; - validateNonEmptyString(spaceId, 'spaceId'); - validateNonEmptyString(environmentId, 'environmentId'); + validateNonEmptyArray(entries, 'Entries'); - if (!contentTypes) { - throw new Error('contentTypes is required and cannot be null or undefined'); + // Validate contentTypeIds + if (!contentTypeIds) { + throw new Error('contentTypeIds is required and cannot be null or undefined'); } - validateNonEmptyArray(contentTypes, 'contentTypes'); + validateNonEmptyArray(contentTypeIds, 'contentTypeIds'); // Validate each entry entries.forEach((entry, i) => { @@ -446,14 +442,6 @@ function validateCreateEntriesInput( }); } -/** - * Creates multiple entries in Contentful - * - * @param cma - Contentful Management API client - * @param entries - Array of entries from Document Parser Agent output - * @param config - Space and environment configuration - * @returns Promise resolving to creation results with entries and errors - */ /** * Extracts unique image tokens from an entry's RichText fields */ @@ -490,9 +478,10 @@ function extractImageTokensFromEntry( * Creates assets for image tokens in parallel and returns mapping results */ async function createAssetsForTokens( - cma: PlainClientAPI, + cma: PageAppSDK['cma'] | ConfigAppSDK['cma'], spaceId: string, environmentId: string, + defaultLocale: string, imageTokenMap: Map ): Promise> { const assetCreationPromises = Array.from(imageTokenMap.entries()).map( @@ -504,12 +493,19 @@ async function createAssetsForTokens( } try { - const asset = await createAssetFromUrlFast(cma, spaceId, environmentId, metadata.url, { - title: metadata.altText || metadata.fileName || 'Image', - altText: metadata.altText, - fileName: metadata.fileName, - contentType: metadata.contentType, - }); + const asset = await createAssetFromUrlFast( + cma, + spaceId, + environmentId, + metadata.url, + defaultLocale, + { + title: metadata.altText || metadata.fileName || 'Image', + altText: metadata.altText, + fileName: metadata.fileName, + contentType: metadata.contentType, + } + ); return { tokenKey, @@ -568,13 +564,21 @@ function buildUrlToAssetIdMap( return urlToAssetId; } +/** + * Creates multiple entries in Contentful + * + * @param sdk - Contentful SDK instance (PageAppSDK or ConfigAppSDK) + * @param entries - Array of entries from Document Parser Agent output + * @param contentTypeIds - Array of content type IDs to fetch and use + * @returns Promise resolving to creation results with entries and errors + */ export async function createEntriesFromPreview( - cma: PlainClientAPI, + sdk: PageAppSDK | ConfigAppSDK, entries: EntryToCreate[], - config: { spaceId: string; environmentId: string; contentTypes: ContentTypeProps[] } + contentTypeIds: string[] ): Promise { try { - validateCreateEntriesInput(cma, entries, config); + validateCreateEntriesInput(sdk, entries, contentTypeIds); } catch (validationError) { const errorMessage = validationError instanceof Error ? validationError.message : String(validationError); @@ -583,14 +587,39 @@ export async function createEntriesFromPreview( errors: [ { contentTypeId: 'validation', - error: `Input validation failed: ${errorMessage}`, + error: errorMessage, details: validationError, }, ], }; } - const { spaceId, environmentId, contentTypes } = config; + const spaceId = sdk.ids.space; + const environmentId = sdk.ids.environment; + const cma = sdk.cma; + const defaultLocale = sdk.locales.default; + + // Fetch content types + const contentTypesResponse = await cma.contentType.getMany({ + spaceId, + environmentId, + }); + const contentTypes = contentTypesResponse.items.filter((ct) => + contentTypeIds.includes(ct.sys.id) + ); + + if (contentTypes.length === 0) { + return { + createdEntries: [], + errors: [ + { + contentTypeId: 'validation', + error: 'No matching content types found', + }, + ], + }; + } + const createdEntries: EntryProps[] = []; const errors: Array<{ contentTypeId: string; error: string; details?: any }> = []; @@ -603,7 +632,13 @@ export async function createEntriesFromPreview( const imageTokenMap = extractImageTokensFromEntry(entry, contentType); if (imageTokenMap.size > 0) { - const results = await createAssetsForTokens(cma, spaceId, environmentId, imageTokenMap); + const results = await createAssetsForTokens( + cma, + spaceId, + environmentId, + defaultLocale, + imageTokenMap + ); urlToAssetId = buildUrlToAssetIdMap(results, imageTokenMap); } } diff --git a/apps/google-docs/src/utils/appActionUtils.ts b/apps/google-docs/src/utils/appActionUtils.ts index 118426665e..25bbfee32e 100644 --- a/apps/google-docs/src/utils/appActionUtils.ts +++ b/apps/google-docs/src/utils/appActionUtils.ts @@ -1,5 +1,4 @@ import { PageAppSDK, ConfigAppSDK } from '@contentful/app-sdk'; -import { EntryToCreate } from '../../functions/agents/documentParserAgent/schema'; /** * Fetches the app action ID by name from the current environment @@ -101,18 +100,3 @@ export const createPreviewAction = async ( oauthToken, }); }; - -/** - * Creates entries in Contentful from parsed document data - * @param sdk - The Contentful SDK instance - * @param entries - Array of entries to create - * @param contentTypeIds - Array of content type IDs used in the entries - * @returns Creation result with created entries and errors - */ -export const createEntriesAction = async ( - sdk: PageAppSDK | ConfigAppSDK, - entries: EntryToCreate[], - contentTypeIds: string[] -) => { - return callAppAction(sdk, 'createEntries', { entries, contentTypeIds }); -}; diff --git a/apps/google-docs/functions/service/utils/richtext.ts b/apps/google-docs/src/utils/richtext.ts similarity index 100% rename from apps/google-docs/functions/service/utils/richtext.ts rename to apps/google-docs/src/utils/richtext.ts diff --git a/apps/google-docs/test/mocks/index.ts b/apps/google-docs/test/mocks/index.ts index d520ea76dd..5e79fe9b86 100644 --- a/apps/google-docs/test/mocks/index.ts +++ b/apps/google-docs/test/mocks/index.ts @@ -1,2 +1,2 @@ -export { mockCma } from './mockCma'; -export { mockSdk } from './mockSdk'; +export { mockCma, createMockCMA } from './mockCma'; +export { mockSdk, createMockSDK } from './mockSdk'; diff --git a/apps/google-docs/test/mocks/mockCma.ts b/apps/google-docs/test/mocks/mockCma.ts index e375acd831..45ae8f6708 100644 --- a/apps/google-docs/test/mocks/mockCma.ts +++ b/apps/google-docs/test/mocks/mockCma.ts @@ -1,3 +1,32 @@ -const mockCma: any = {}; +import { vi } from 'vitest'; + +export const createMockCMA = () => { + return { + asset: { + create: vi.fn(), + processForAllLocales: vi.fn(), + get: vi.fn(), + publish: vi.fn(), + }, + entry: { + create: vi.fn(), + }, + contentType: { + getMany: vi.fn().mockResolvedValue({ items: [], total: 0 }), + }, + space: { + get: vi.fn().mockResolvedValue({ sys: { id: 'test-space-id' } }), + }, + environment: { + get: vi.fn().mockResolvedValue({ sys: { id: 'test-environment-id' } }), + }, + appAction: { + getManyForEnvironment: vi.fn().mockResolvedValue({ items: [] }), + }, + }; +}; + +// Default mock CMA instance for backward compatibility +const mockCma: any = createMockCMA(); export { mockCma }; diff --git a/apps/google-docs/test/mocks/mockSdk.ts b/apps/google-docs/test/mocks/mockSdk.ts index 6eef708d90..536fcde960 100644 --- a/apps/google-docs/test/mocks/mockSdk.ts +++ b/apps/google-docs/test/mocks/mockSdk.ts @@ -1,15 +1,41 @@ import { vi } from 'vitest'; +import type { PageAppSDK, ConfigAppSDK } from '@contentful/app-sdk'; +import { createMockCMA } from './mockCma'; -const mockSdk: any = { - app: { +export const createMockSDK = ( + overrides?: Partial +): PageAppSDK | ConfigAppSDK => { + const mockCMA = createMockCMA(); + + const mockApp = { onConfigure: vi.fn(), - getParameters: vi.fn().mockReturnValueOnce({}), + onConfigurationCompleted: vi.fn(), + getParameters: vi.fn().mockReturnValue({}), setReady: vi.fn(), getCurrentState: vi.fn(), - }, - ids: { - app: 'test-app', - }, + }; + + const mockNotifier = { + error: vi.fn(), + warning: vi.fn(), + success: vi.fn(), + info: vi.fn(), + }; + + return { + cma: mockCMA as any, + ids: { + space: 'test-space-id', + environment: 'test-environment-id', + app: 'test-app', + ...overrides?.ids, + }, + app: mockApp as any, + notifier: mockNotifier as any, + ...overrides, + } as unknown as PageAppSDK | ConfigAppSDK; }; +const mockSdk: any = createMockSDK(); + export { mockSdk }; From d25d42cb2b9c761597ed1629c9ab0461d8661c2c Mon Sep 17 00:00:00 2001 From: david shibley Date: Fri, 19 Dec 2025 10:30:55 -0700 Subject: [PATCH 06/14] feat: implementing plan preview step --- .../documentParser.agent.ts | 27 +- .../functions/createEntriesFromDocument.ts | 92 +++++ .../functions/createPlanFromDocument.ts | 136 ++++++++ .../page/ContentTypePickerModal.tsx | 22 +- .../src/components/page/LoadingModal.tsx | 29 ++ .../src/components/page/PreviewModal.tsx | 323 ++++++++++++++++++ .../src/hooks/useDocumentSubmission.ts | 38 ++- .../src/hooks/useModalManagement.ts | 10 + .../src/hooks/usePlanGeneration.ts | 155 +++++++++ apps/google-docs/src/locations/Page.tsx | 118 ++++++- .../google-docs/src/utils/appFunctionUtils.ts | 99 ++++++ .../google-docs/src/utils/googleDriveUtils.ts | 29 ++ 12 files changed, 1027 insertions(+), 51 deletions(-) create mode 100644 apps/google-docs/functions/createEntriesFromDocument.ts create mode 100644 apps/google-docs/functions/createPlanFromDocument.ts create mode 100644 apps/google-docs/src/components/page/LoadingModal.tsx create mode 100644 apps/google-docs/src/components/page/PreviewModal.tsx create mode 100644 apps/google-docs/src/hooks/usePlanGeneration.ts create mode 100644 apps/google-docs/src/utils/appFunctionUtils.ts create mode 100644 apps/google-docs/src/utils/googleDriveUtils.ts diff --git a/apps/google-docs/functions/agents/documentParserAgent/documentParser.agent.ts b/apps/google-docs/functions/agents/documentParserAgent/documentParser.agent.ts index 8e6ce4f8b1..386384202c 100644 --- a/apps/google-docs/functions/agents/documentParserAgent/documentParser.agent.ts +++ b/apps/google-docs/functions/agents/documentParserAgent/documentParser.agent.ts @@ -12,14 +12,12 @@ import { createOpenAI } from '@ai-sdk/openai'; import { generateObject } from 'ai'; import { ContentTypeProps } from 'contentful-management'; import { FinalEntriesResultSchema, FinalEntriesResult } from './schema'; -import { fetchGoogleDocAsJson } from '../../service/googleDriveService'; /** * Configuration for the document parser */ export interface DocumentParserConfig { - documentId: string; - oauthToken: string; + documentJson: unknown; // Google Doc JSON (fetched from frontend) contentTypes: ContentTypeProps[]; openAiApiKey: string; locale?: string; @@ -40,15 +38,22 @@ export async function createPreviewWithAgent( const modelVersion = 'gpt-4o'; const temperature = 0.3; - const { documentId, oauthToken, openAiApiKey, contentTypes, locale = 'en-US' } = config; + const { documentJson, openAiApiKey, contentTypes, locale = 'en-US' } = config; const openaiClient = createOpenAI({ apiKey: openAiApiKey, }); - console.log('Document Parser Agent document content Input:', documentId); - const documentJson = await fetchGoogleDocAsJson({ documentId, oauthToken }); + console.log('Document Parser Agent document JSON received (fetched from frontend)'); + + // Step 1: Build extraction prompt + const promptStartTime = Date.now(); const prompt = buildExtractionPrompt({ contentTypes, documentJson, locale }); + const promptDuration = Date.now() - promptStartTime; + console.log(`[Timing] Build extraction prompt: ${promptDuration}ms`); + + // Step 3: Generate object with AI + const aiStartTime = Date.now(); const result = await generateObject({ model: openaiClient(modelVersion), schema: FinalEntriesResultSchema, @@ -56,9 +61,17 @@ export async function createPreviewWithAgent( system: buildSystemPrompt(), prompt, }); + const aiDuration = Date.now() - aiStartTime; + console.log(`[Timing] AI generateObject: ${aiDuration}ms`); + // Step 4: Process result + const processStartTime = Date.now(); const finalResult = result.object as FinalEntriesResult; - console.log('Document Parser Agent Result:', JSON.stringify(result, null, 2)); + const processDuration = Date.now() - processStartTime; + console.log(`[Timing] Process result: ${processDuration}ms`); + + const totalDuration = Date.now() - promptStartTime; + console.log(`[Timing] Total Document Parser Agent execution: ${totalDuration}ms`); return finalResult; } diff --git a/apps/google-docs/functions/createEntriesFromDocument.ts b/apps/google-docs/functions/createEntriesFromDocument.ts new file mode 100644 index 0000000000..1c98ccf22c --- /dev/null +++ b/apps/google-docs/functions/createEntriesFromDocument.ts @@ -0,0 +1,92 @@ +import type { + FunctionEventContext, + FunctionEventHandler, + FunctionTypeEnum, + AppActionRequest, +} from '@contentful/node-apps-toolkit'; +import { analyzeDocumentWithAgent } from './agents/documentParserAgent/documentParser.agent'; +import { fetchContentTypes } from './service/contentTypeService'; +import { initContentfulManagementClient } from './service/initCMAClient'; +import { createEntries } from './service/entryService'; +import { EntryToCreate } from './agents/documentParserAgent/schema'; + +export type AppActionParameters = { + contentTypeIds: string[]; + documentJson?: unknown; // Optional: Google Doc JSON (if entries not provided, will analyze document) + entries?: EntryToCreate[]; // Optional: if provided, skip document analysis and use these entries +}; + +interface AppInstallationParameters { + openAiApiKey: string; +} + +/* + * Important Caveat: App Functions have a 30 second limit on execution time. + * There is a likely future where we will need to break down the function into smaller functions. + */ +export const handler: FunctionEventHandler< + FunctionTypeEnum.AppActionCall, + AppActionParameters +> = async ( + event: AppActionRequest<'Custom', AppActionParameters>, + context: FunctionEventContext +) => { + const { contentTypeIds, documentJson, entries: providedEntries } = event.body; + const { openAiApiKey } = context.appInstallationParameters as AppInstallationParameters; + + if (!contentTypeIds || contentTypeIds.length === 0) { + throw new Error('At least one content type ID is required'); + } + + const cma = initContentfulManagementClient(context); + const contentTypes = await fetchContentTypes(cma, new Set(contentTypeIds)); + + // If entries are provided (from plan), use them directly instead of re-analyzing + let entriesToCreate: EntryToCreate[]; + let summary: string; + let totalEntries: number; + + if (providedEntries && Array.isArray(providedEntries) && providedEntries.length > 0) { + // Use provided entries from plan - skip document analysis + entriesToCreate = providedEntries; + summary = `Creating ${entriesToCreate.length} entries from plan`; + totalEntries = entriesToCreate.length; + } else if (documentJson) { + // Fallback: analyze document if entries not provided but documentJson is available + const aiDocumentResponse = await analyzeDocumentWithAgent({ + documentJson, + openAiApiKey, + contentTypes, + }); + entriesToCreate = aiDocumentResponse.entries; + summary = aiDocumentResponse.summary; + totalEntries = aiDocumentResponse.totalEntries; + } else { + throw new Error('Either entries or documentJson must be provided'); + } + + // INTEG-3264: Create the entries in Contentful using the entry service + const creationResult = await createEntries(cma, entriesToCreate, { + spaceId: context.spaceId, + environmentId: context.environmentId, + contentTypes, + }); + console.log('Created Entries Result:', creationResult); + + // INTEG-3265: Create the assets in Contentful using the asset service + // await createAssets() + + return { + success: true, + response: { + summary, + totalEntriesExtracted: totalEntries, + createdEntries: creationResult.createdEntries.map((entry) => ({ + id: entry.sys.id, + contentType: entry.sys.contentType.sys.id, + })), + errors: creationResult.errors, + successRate: `${creationResult.createdEntries.length}/${totalEntries}`, + }, + }; +}; diff --git a/apps/google-docs/functions/createPlanFromDocument.ts b/apps/google-docs/functions/createPlanFromDocument.ts new file mode 100644 index 0000000000..018b5165b7 --- /dev/null +++ b/apps/google-docs/functions/createPlanFromDocument.ts @@ -0,0 +1,136 @@ +import type { + FunctionEventContext, + FunctionEventHandler, + FunctionTypeEnum, + AppActionRequest, +} from '@contentful/node-apps-toolkit'; +import { analyzeDocumentWithAgent } from './agents/documentParserAgent/documentParser.agent'; +import { fetchContentTypes } from './service/contentTypeService'; +import { initContentfulManagementClient } from './service/initCMAClient'; +import { EntryToCreate } from './agents/documentParserAgent/schema'; +import { ContentTypeProps } from 'contentful-management'; + +export type AppActionParameters = { + contentTypeIds: string[]; + documentJson: unknown; // Google Doc JSON (fetched from frontend) +}; + +interface AppInstallationParameters { + openAiApiKey: string; +} + +interface AssetInfo { + url: string; + altText: string; + fileName: string; +} + +/** + * Extracts asset information from entries by scanning RichText fields for image tokens + */ +function extractAssetsFromEntries( + entries: EntryToCreate[], + contentTypes: ContentTypeProps[] +): AssetInfo[] { + const IMAGE_TOKEN_REGEX = /!\[([^\]]*?)\]\(([\s\S]*?)\)/g; + const assetsMap = new Map(); + + for (const entry of entries) { + const contentType = contentTypes.find((ct) => ct.sys.id === entry.contentTypeId); + if (!contentType) continue; + + // Check all RichText fields + for (const field of contentType.fields) { + if (field.type !== 'RichText') continue; + + const localizedValue = entry.fields[field.id]; + if (!localizedValue || typeof localizedValue !== 'object') continue; + + // Check all locales + for (const value of Object.values(localizedValue)) { + if (typeof value !== 'string') continue; + + // Reset regex state + IMAGE_TOKEN_REGEX.lastIndex = 0; + for (const match of value.matchAll(IMAGE_TOKEN_REGEX)) { + const altText = (match[1] || '').trim(); + const url = String(match[2]).replace(/\s+/g, '').trim(); + + if (!url) continue; + + // Extract filename from URL + let fileName = 'image'; + try { + const urlObj = new URL(url); + const pathname = urlObj.pathname.toLowerCase(); + const pathParts = pathname.split('/').filter(Boolean); + fileName = pathParts[pathParts.length - 1] || 'image'; + // Remove query params if present + fileName = fileName.split('?')[0]; + } catch { + // If URL parsing fails, use default + } + + // Use URL as key to avoid duplicates + if (!assetsMap.has(url)) { + assetsMap.set(url, { + url, + altText: altText || fileName, + fileName: fileName || 'image', + }); + } + } + } + } + } + + return Array.from(assetsMap.values()); +} + +/** + * App function handler that generates a plan (proposed entries) from a Google Doc + * without actually creating the entries. This allows users to review what will be created. + */ +export const handler: FunctionEventHandler< + FunctionTypeEnum.AppActionCall, + AppActionParameters +> = async ( + event: AppActionRequest<'Custom', AppActionParameters>, + context: FunctionEventContext +) => { + const { contentTypeIds, documentJson } = event.body; + const { openAiApiKey } = context.appInstallationParameters as AppInstallationParameters; + + if (!documentJson) { + throw new Error('Document JSON is required'); + } + + if (!contentTypeIds || contentTypeIds.length === 0) { + throw new Error('At least one content type ID is required'); + } + + const cma = initContentfulManagementClient(context); + const contentTypes = await fetchContentTypes(cma, new Set(contentTypeIds)); + console.log('Content types:', contentTypes); + + // Use the same AI agent to analyze the document and generate proposed entries + const aiDocumentResponse = await analyzeDocumentWithAgent({ + documentJson, + openAiApiKey, + contentTypes, + }); + console.log('AI document response:', aiDocumentResponse); + // Extract asset information from entries + const assets = extractAssetsFromEntries(aiDocumentResponse.entries, contentTypes); + + console.log('Assets:', assets); + // Return plan data without creating entries + return { + success: true, + response: { + ...aiDocumentResponse, + assets, + totalAssets: assets.length, + }, + }; +}; diff --git a/apps/google-docs/src/components/page/ContentTypePickerModal.tsx b/apps/google-docs/src/components/page/ContentTypePickerModal.tsx index 451626cde4..9607291db1 100644 --- a/apps/google-docs/src/components/page/ContentTypePickerModal.tsx +++ b/apps/google-docs/src/components/page/ContentTypePickerModal.tsx @@ -22,7 +22,7 @@ interface ContentTypePickerModalProps { isOpen: boolean; onClose: () => void; onSelect: (contentTypes: SelectedContentType[]) => void; - isSubmitting: boolean; + isGeneratingPlan: boolean; selectedContentTypes: SelectedContentType[]; setSelectedContentTypes: ( contentTypes: SelectedContentType[] | ((prev: SelectedContentType[]) => SelectedContentType[]) @@ -34,7 +34,7 @@ export const ContentTypePickerModal = ({ isOpen, onClose, onSelect, - isSubmitting, + isGeneratingPlan, selectedContentTypes, setSelectedContentTypes, }: ContentTypePickerModalProps) => { @@ -88,7 +88,7 @@ export const ContentTypePickerModal = ({ }, [isOpen]); const handleAddContentType = (contentTypeId: string) => { - if (!contentTypeId || isSubmitting) return; + if (!contentTypeId || isGeneratingPlan) return; const contentType = contentTypes.find((ct) => ct.sys.id === contentTypeId); if (contentType && !selectedContentTypes.some((ct) => ct.id === contentTypeId)) { @@ -100,12 +100,12 @@ export const ContentTypePickerModal = ({ }; const handleRemoveContentType = (contentTypeId: string) => { - if (isSubmitting) return; + if (isGeneratingPlan) return; setSelectedContentTypes(selectedContentTypes.filter((ct) => ct.id !== contentTypeId)); }; const handleClose = () => { - if (isSubmitting) return; // Prevent closing during submission + if (isGeneratingPlan) return; // Prevent closing during plan generation onClose(); }; @@ -144,7 +144,7 @@ export const ContentTypePickerModal = ({ onChange={(e) => { handleAddContentType(e.target.value); }} - isDisabled={isLoading || availableContentTypes.length === 0 || isSubmitting}> + isDisabled={isLoading || availableContentTypes.length === 0 || isGeneratingPlan}> {isLoading ? 'Loading content types...' : 'Select one or more'} @@ -172,22 +172,22 @@ export const ContentTypePickerModal = ({ handleRemoveContentType(ct.id)} + onClose={isGeneratingPlan ? undefined : () => handleRemoveContentType(ct.id)} /> ))} )} - diff --git a/apps/google-docs/src/components/page/LoadingModal.tsx b/apps/google-docs/src/components/page/LoadingModal.tsx new file mode 100644 index 0000000000..9365aec534 --- /dev/null +++ b/apps/google-docs/src/components/page/LoadingModal.tsx @@ -0,0 +1,29 @@ +import { Modal, Paragraph, Spinner, Flex } from '@contentful/f36-components'; + +interface LoadingModalProps { + isOpen: boolean; + message?: string; +} + +export const LoadingModal = ({ isOpen, message = 'Processing...' }: LoadingModalProps) => { + return ( + {}} + size="medium" + shouldCloseOnOverlayClick={false} + shouldCloseOnEscapePress={false}> + {() => ( + <> + + + + + {message} + + + + )} + + ); +}; diff --git a/apps/google-docs/src/components/page/PreviewModal.tsx b/apps/google-docs/src/components/page/PreviewModal.tsx new file mode 100644 index 0000000000..09106d34ca --- /dev/null +++ b/apps/google-docs/src/components/page/PreviewModal.tsx @@ -0,0 +1,323 @@ +import { Button, Modal, Paragraph, Box, Heading, List, Flex } from '@contentful/f36-components'; +import { EntryToCreate } from '../../../functions/agents/documentParserAgent/schema'; + +interface AssetInfo { + url: string; + altText: string; + fileName: string; +} + +interface PlanData { + summary: string; + totalEntries: number; + entries: EntryToCreate[]; + assets?: AssetInfo[]; + totalAssets?: number; +} + +interface PlanReviewModalProps { + isOpen: boolean; + onClose: () => void; + onCreateEntries: () => void; + plan: PlanData | null; + isLoading: boolean; +} + +/** + * Extracts a display title from an entry by looking for common title field names + */ +function extractEntryTitle(entry: EntryToCreate, locale: string = 'en-US'): string { + if (!entry || !entry.fields) { + return 'Untitled Entry'; + } + + // Common field names that might contain titles + const titleFieldNames = ['title', 'name', 'heading', 'headline', 'label']; + + for (const fieldName of titleFieldNames) { + const field = entry.fields[fieldName]; + if (field && typeof field === 'object') { + const value = field[locale] || Object.values(field)[0]; + if (typeof value === 'string' && value.trim()) { + return value.trim(); + } + } + } + + // If no title field found, try to get first non-empty string field + for (const [fieldId, localizedValue] of Object.entries(entry.fields)) { + if (localizedValue && typeof localizedValue === 'object') { + const value = localizedValue[locale] || Object.values(localizedValue)[0]; + if (typeof value === 'string' && value.trim()) { + return value.trim(); + } + } + } + + // Fallback to content type ID + return `Entry (${entry.contentTypeId || 'unknown'})`; +} + +/** + * Builds a hierarchical structure from entries + * For now, we'll infer relationships based on content type patterns + * In the future, this could be enhanced with explicit dependency data from the AI + */ +interface EntryNode { + entry: EntryToCreate; + title: string; + children: EntryNode[]; + level: number; +} + +function buildEntryHierarchy(entries: EntryToCreate[]): EntryNode[] { + if (!entries || !Array.isArray(entries)) { + return []; + } + + const nodes: Array = entries + .map((entry) => { + if (!entry) { + return null; + } + const title = extractEntryTitle(entry); + return { + entry, + title, + children: [], + level: 0, + }; + }) + .filter((node) => node !== null); + + // Simple heuristic: group entries by content type patterns + // Articles/Pages are typically parents, Sections/FAQs are children + const parentTypes = ['article', 'page', 'blog', 'post']; + const childTypes = ['section', 'faq', 'item', 'block']; + + const parents: EntryNode[] = []; + const children: EntryNode[] = []; + + nodes.forEach((node) => { + if (!node) { + return; + } + const contentTypeLower = node.entry.contentTypeId.toLowerCase(); + const isParent = parentTypes.some((type) => contentTypeLower.includes(type)); + const isChild = childTypes.some((type) => contentTypeLower.includes(type)); + + if (isParent && !isChild) { + parents.push(node); + } else if (isChild) { + children.push(node); + } else { + // Default to parent if unclear + parents.push(node); + } + }); + + // If we have one parent and multiple children, attach children to parent + if (parents.length === 1 && children.length > 0) { + parents[0].children = children.map((child) => ({ ...child, level: 1 })); + return parents; + } + + // Otherwise return flat structure + return nodes.filter((node): node is EntryNode => node !== null); +} + +export const PreviewModal = ({ + isOpen, + onClose, + onCreateEntries, + plan, + isLoading, +}: PlanReviewModalProps) => { + const handleCreateEntries = () => { + onCreateEntries(); + }; + + const handleClose = () => { + if (!isLoading) { + onClose(); + } + }; + + if (!plan) { + return null; + } + + const entries = plan.entries || []; + const totalEntries = plan.totalEntries ?? entries.length; + const assets = plan.assets || []; + const totalAssets = plan.totalAssets ?? assets.length; + + // Only build hierarchy if we have entries + const entryHierarchy = entries.length > 0 ? buildEntryHierarchy(entries) : []; + + const renderEntryNode = ( + node: EntryNode, + index: number, + isLast: boolean, + isChild: boolean = false, + parentHasMoreSiblings: boolean = false + ) => { + const hasChildren = node.children.length > 0; + const indent = node.level * 24; + + // Extract content type name from the title (text in parentheses) + const titleMatch = + node.title && typeof node.title === 'string' ? node.title.match(/^(.+?)\s*\((.+?)\)$/) : null; + const displayTitle = + titleMatch && titleMatch[1] ? titleMatch[1].trim() : node.title || 'Untitled'; + const contentTypeName = + titleMatch && titleMatch[2] ? titleMatch[2].trim() : node.entry?.contentTypeId || ''; + + return ( + + + {/* Vertical line for children - extends from parent */} + {isChild && ( + <> + {/* Vertical line */} + + {/* Horizontal connector line */} + + + )} + + {/* Entry card */} + + {/* Title with content type */} + + + {displayTitle.length > 40 ? displayTitle.substring(0, 40) + '...' : displayTitle} + + {contentTypeName && ( + + ({contentTypeName}) + + )} + + + + + {/* Render children */} + {hasChildren && ( + + {/* Vertical line connecting all children */} + + + {node.children.map((child, childIndex) => + renderEntryNode( + child, + childIndex, + childIndex === node.children.length - 1, + true, + childIndex < node.children.length - 1 + ) + )} + + + )} + + ); + }; + + return ( + + {() => ( + <> + + + + {plan.summary || + `Based off the document, ${totalEntries} ${ + totalEntries === 1 ? 'entry is' : 'entries are' + } being suggested:`} + + + + {entryHierarchy.length > 0 ? ( + + {entryHierarchy.map((node, index) => + renderEntryNode(node, index, index === entryHierarchy.length - 1) + )} + + ) : ( + No entries found + )} + + + + + + + + )} + + ); +}; diff --git a/apps/google-docs/src/hooks/useDocumentSubmission.ts b/apps/google-docs/src/hooks/useDocumentSubmission.ts index 345678077d..a76989730b 100644 --- a/apps/google-docs/src/hooks/useDocumentSubmission.ts +++ b/apps/google-docs/src/hooks/useDocumentSubmission.ts @@ -1,7 +1,8 @@ import { useState, useCallback } from 'react'; import { PageAppSDK } from '@contentful/app-sdk'; -import { analyzeContentTypesAction, createPreviewAction } from '../utils/appActionUtils'; -import { ERROR_MESSAGES } from '../constants/messages'; +import { createEntriesFromDocumentAction } from '../utils/appFunctionUtils'; +import { fetchGoogleDocAsJson } from '../utils/googleDriveUtils'; +import { ERROR_MESSAGES, SUCCESS_MESSAGES } from '../constants/messages'; import { EntryToCreate } from '../../functions/agents/documentParserAgent/schema'; interface UseDocumentSubmissionReturn { @@ -9,10 +10,16 @@ interface UseDocumentSubmissionReturn { previewEntries: EntryToCreate[]; errorMessage: string | null; successMessage: string | null; - submit: (contentTypeIds: string[]) => Promise; + submit: (contentTypeIds: string[], entries?: any[]) => Promise; clearMessages: () => void; } +interface FinalEntriesResult { + entries: EntryToCreate[]; + summary: string; + totalEntries: number; +} + export const useDocumentSubmission = ( sdk: PageAppSDK, documentId: string, @@ -45,7 +52,7 @@ export const useDocumentSubmission = ( ); const submit = useCallback( - async (contentTypeIds: string[]) => { + async (contentTypeIds: string[], entries?: any[]) => { const validationError = validateSubmission(contentTypeIds); if (validationError) { setErrorMessage(validationError); @@ -59,22 +66,21 @@ export const useDocumentSubmission = ( setPreviewEntries([]); try { - const analyzeContentTypesResponse = await analyzeContentTypesAction( - sdk, - contentTypeIds, - oauthToken - ); - console.log('analyzeContentTypesResponse', analyzeContentTypesResponse); + // If entries provided, use them directly. Otherwise fetch document JSON as fallback + let documentJson: unknown | undefined; + if (!entries || entries.length === 0) { + documentJson = await fetchGoogleDocAsJson(documentId, oauthToken); + } - const processDocumentResponse = await createPreviewAction( + const response = await createEntriesFromDocumentAction( sdk, contentTypeIds, - documentId, - oauthToken - ); - console.log('processDocumentResponse', processDocumentResponse); + entries, + documentJson + ) as any; + const result: FinalEntriesResult = response.sys.result; - setPreviewEntries((processDocumentResponse as any).sys.result.entries); + setPreviewEntries(result.entries); } catch (error) { setErrorMessage(error instanceof Error ? error.message : ERROR_MESSAGES.SUBMISSION_FAILED); } finally { diff --git a/apps/google-docs/src/hooks/useModalManagement.ts b/apps/google-docs/src/hooks/useModalManagement.ts index 1430bb6a29..95df701ab3 100644 --- a/apps/google-docs/src/hooks/useModalManagement.ts +++ b/apps/google-docs/src/hooks/useModalManagement.ts @@ -7,6 +7,7 @@ interface ModalStates { isPreviewModalOpen: boolean; isReviewModalOpen: boolean; isErrorEntriesModalOpen: boolean; + isLoadingModalOpen: boolean; } interface ModalSetters { @@ -16,6 +17,7 @@ interface ModalSetters { setIsPreviewModalOpen: (value: boolean) => void; setIsReviewModalOpen: (value: boolean) => void; setIsErrorEntriesModalOpen: (value: boolean) => void; + setIsLoadingModalOpen: (value: boolean) => void; } export enum ModalType { @@ -25,6 +27,7 @@ export enum ModalType { PREVIEW = 'preview', REVIEW = 'review', ERROR_ENTRIES = 'errorEntries', + LOADING = 'loading', } export const useModalManagement = () => { @@ -34,6 +37,7 @@ export const useModalManagement = () => { const [isPreviewModalOpen, setIsPreviewModalOpen] = useState(false); const [isReviewModalOpen, setIsReviewModalOpen] = useState(false); const [isErrorEntriesModalOpen, setIsErrorEntriesModalOpen] = useState(false); + const [isLoadingModalOpen, setIsLoadingModalOpen] = useState(false); const openModal = (modalType: ModalType) => { switch (modalType) { @@ -54,6 +58,8 @@ export const useModalManagement = () => { break; case ModalType.ERROR_ENTRIES: setIsErrorEntriesModalOpen(true); + case ModalType.LOADING: + setIsLoadingModalOpen(true); break; } }; @@ -77,6 +83,8 @@ export const useModalManagement = () => { break; case ModalType.ERROR_ENTRIES: setIsErrorEntriesModalOpen(false); + case ModalType.LOADING: + setIsLoadingModalOpen(false); break; } }; @@ -89,6 +97,7 @@ export const useModalManagement = () => { isPreviewModalOpen, isReviewModalOpen, isErrorEntriesModalOpen, + isLoadingModalOpen, } as ModalStates, setModalStates: { setIsUploadModalOpen, @@ -97,6 +106,7 @@ export const useModalManagement = () => { setIsPreviewModalOpen, setIsReviewModalOpen, setIsErrorEntriesModalOpen, + setIsLoadingModalOpen, } as ModalSetters, openModal, closeModal, diff --git a/apps/google-docs/src/hooks/usePlanGeneration.ts b/apps/google-docs/src/hooks/usePlanGeneration.ts new file mode 100644 index 0000000000..22559c4889 --- /dev/null +++ b/apps/google-docs/src/hooks/usePlanGeneration.ts @@ -0,0 +1,155 @@ +import { useState, useCallback } from 'react'; +import { PageAppSDK } from '@contentful/app-sdk'; +import { createPlanFromDocumentAction } from '../utils/appFunctionUtils'; +import { fetchGoogleDocAsJson } from '../utils/googleDriveUtils'; +import { ERROR_MESSAGES } from '../constants/messages'; +import { EntryToCreate } from '../../functions/agents/documentParserAgent/schema'; + +interface AssetInfo { + url: string; + altText: string; + fileName: string; +} + +interface PlanData { + summary: string; + totalEntries: number; + entries: EntryToCreate[]; + assets?: AssetInfo[]; + totalAssets?: number; +} + +interface UsePlanGenerationReturn { + plan: PlanData | null; + isLoading: boolean; + error: string | null; + generatePlan: (contentTypeIds: string[]) => Promise; + clearPlan: () => void; +} + +export const usePlanGeneration = ( + sdk: PageAppSDK, + documentId: string, + oauthToken: string +): UsePlanGenerationReturn => { + const [plan, setPlan] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const validatePlanGeneration = useCallback( + (contentTypeIds: string[]): string | null => { + const openAiApiKey = sdk.parameters.installation?.openAiApiKey as string | undefined; + + if (!openAiApiKey || !openAiApiKey.trim()) { + return ERROR_MESSAGES.NO_API_KEY; + } + + if (!documentId || !documentId.trim()) { + return ERROR_MESSAGES.NO_DOCUMENT; + } + + if (contentTypeIds.length === 0) { + return ERROR_MESSAGES.NO_CONTENT_TYPE; + } + + return null; + }, + [sdk, documentId] + ); + + const generatePlan = useCallback( + async (contentTypeIds: string[]) => { + const validationError = validatePlanGeneration(contentTypeIds); + if (validationError) { + setError(validationError); + setPlan(null); + return; + } + + setIsLoading(true); + setError(null); + setPlan(null); + + try { + // Fetch document JSON from frontend + const documentJson = await fetchGoogleDocAsJson(documentId, oauthToken); + + const response = await createPlanFromDocumentAction(sdk, contentTypeIds, documentJson); + console.log('Plan generation response:', response); + + if (response.sys?.status === 'succeeded' && response.sys?.result) { + const result = response.sys.result as { + success?: boolean; + response?: { + summary: string; + totalEntries: number; + entries: EntryToCreate[]; + assets?: AssetInfo[]; + totalAssets?: number; + }; + summary?: string; + totalEntries?: number; + entries?: EntryToCreate[]; + assets?: AssetInfo[]; + totalAssets?: number; + }; + + // Handle both response structures: direct result or wrapped in response object + const planData = result.response || result; + + // Type guard to ensure planData has the required properties + if ( + planData && + typeof planData === 'object' && + 'summary' in planData && + 'totalEntries' in planData && + 'entries' in planData && + typeof planData.summary === 'string' && + typeof planData.totalEntries === 'number' && + Array.isArray(planData.entries) + ) { + // Safely extract assets and totalAssets with proper type checking + const assets = + 'assets' in planData && Array.isArray(planData.assets) + ? (planData.assets as AssetInfo[]) + : undefined; + const totalAssets = + 'totalAssets' in planData && typeof planData.totalAssets === 'number' + ? planData.totalAssets + : undefined; + + setPlan({ + summary: planData.summary, + totalEntries: planData.totalEntries, + entries: planData.entries, + assets, + totalAssets, + }); + } else { + setError('Invalid plan data structure received'); + } + } else { + setError('Failed to generate plan'); + } + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to generate plan'); + } finally { + setIsLoading(false); + } + }, + [sdk, documentId, oauthToken, validatePlanGeneration] + ); + + const clearPlan = useCallback(() => { + setPlan(null); + setError(null); + }, []); + + return { + plan, + isLoading, + error, + generatePlan, + clearPlan, + }; +}; diff --git a/apps/google-docs/src/locations/Page.tsx b/apps/google-docs/src/locations/Page.tsx index da3abe92a9..3c92704d5a 100644 --- a/apps/google-docs/src/locations/Page.tsx +++ b/apps/google-docs/src/locations/Page.tsx @@ -7,9 +7,12 @@ import { SelectedContentType, } from '../components/page/ContentTypePickerModal'; import { ConfirmCancelModal } from '../components/page/ConfirmCancelModal'; +import { PreviewModal } from '../components/page/PreviewModal'; +import { LoadingModal } from '../components/page/LoadingModal'; import { useModalManagement, ModalType } from '../hooks/useModalManagement'; import { useProgressTracking } from '../hooks/useProgressTracking'; import { useDocumentSubmission } from '../hooks/useDocumentSubmission'; +import { usePlanGeneration } from '../hooks/usePlanGeneration'; import SelectDocumentModal from '../components/page/SelectDocumentModal'; import { ViewPreviewModal } from '../components/page/ViewPreviewModal'; import { ReviewEntriesModal } from '../components/page/ReviewEntriesModal'; @@ -32,11 +35,15 @@ const Page = () => { pendingCloseAction, setPendingCloseAction, } = useProgressTracking(); - const { previewEntries, submit, clearMessages, isSubmitting } = useDocumentSubmission( - sdk, - documentId, - oauthToken - ); + const { previewEntries, successMessage, errorMessage, submit, clearMessages, isSubmitting } = + useDocumentSubmission(sdk, documentId, oauthToken); + const { + plan, + isLoading: isGeneratingPlan, + error: planError, + generatePlan, + clearPlan, + } = usePlanGeneration(sdk, documentId, oauthToken); // Track previous submission state to detect completion const prevIsSubmittingRef = useRef(false); @@ -53,7 +60,10 @@ const Page = () => { resetProgressTracking(); closeModal(ModalType.UPLOAD); closeModal(ModalType.CONTENT_TYPE_PICKER); + closeModal(ModalType.PREVIEW); + closeModal(ModalType.LOADING); clearMessages(); + clearPlan(); }; const handleUploadModalCloseRequest = (docId?: string) => { @@ -161,23 +171,84 @@ const Page = () => { resetProgress(); }; - // Close the ContentTypePickerModal when submission completes and open preview modal + + const handlePlanReviewCancel = () => { + closeModal(ModalType.PREVIEW); + openModal(ModalType.CONTENT_TYPE_PICKER); + clearPlan(); + }; + + const handlePlanReviewCreateEntries = async () => { + if (!plan) return; + + const contentTypeIds = plan.entries.map((entry) => entry.contentTypeId); + const uniqueContentTypeIds = Array.from(new Set(contentTypeIds)); + + closeModal(ModalType.PREVIEW); + openModal(ModalType.LOADING); + + // Create entries using the plan data - pass entries to avoid re-analysis + await submit(uniqueContentTypeIds, plan.entries); + }; + + // Close the Loading modal when submission completes useEffect(() => { const submissionJustCompleted = prevIsSubmittingRef.current && !isSubmitting; - if (submissionJustCompleted && modalStates.isContentTypePickerOpen) { - console.log('Document processing completed, previewEntries:', previewEntries); - closeModal(ModalType.CONTENT_TYPE_PICKER); - - // Open preview modal if we have entries - if (previewEntries && previewEntries.length > 0) { - console.log('Opening preview modal with', previewEntries.length, 'entries'); - openModal(ModalType.PREVIEW); - } + if (submissionJustCompleted && modalStates.isLoadingModalOpen) { + closeModal(ModalType.LOADING); + clearPlan(); } prevIsSubmittingRef.current = isSubmitting; - }, [isSubmitting, modalStates.isContentTypePickerOpen, closeModal, openModal, previewEntries]); + }, [isSubmitting, modalStates.isLoadingModalOpen, closeModal, clearPlan]); + + // Handle plan generation completion + useEffect(() => { + if (!isGeneratingPlan && plan && !planError) { + // Close loading modal first + if (modalStates.isLoadingModalOpen) { + closeModal(ModalType.LOADING); + } + + // Validate plan has required data before opening modal + if (plan.entries && Array.isArray(plan.entries) && plan.summary) { + // Plan generation completed successfully, open plan review modal + if (!modalStates.isPreviewModalOpen && !modalStates.isContentTypePickerOpen) { + openModal(ModalType.PREVIEW); + } + } else { + // Invalid plan data + sdk.notifier.error('Invalid plan data received'); + if (modalStates.isPreviewModalOpen) { + closeModal(ModalType.PREVIEW); + } + openModal(ModalType.CONTENT_TYPE_PICKER); + } + } else if (!isGeneratingPlan && planError) { + // Plan generation failed, close loading modal and return to content type picker + if (modalStates.isLoadingModalOpen) { + closeModal(ModalType.LOADING); + } + sdk.notifier.error(`Failed to generate plan: ${planError}`); + if (modalStates.isPreviewModalOpen) { + closeModal(ModalType.PREVIEW); + } + if (!modalStates.isContentTypePickerOpen) { + openModal(ModalType.CONTENT_TYPE_PICKER); + } + } + }, [ + isGeneratingPlan, + plan, + planError, + modalStates.isLoadingModalOpen, + modalStates.isPreviewModalOpen, + modalStates.isContentTypePickerOpen, + openModal, + closeModal, + sdk, + ]); return ( <> @@ -196,11 +267,24 @@ const Page = () => { isOpen={modalStates.isContentTypePickerOpen} onClose={handleContentTypePickerCloseRequest} onSelect={handleContentTypeSelected} - isSubmitting={isSubmitting} + isGeneratingPlan={isGeneratingPlan} selectedContentTypes={selectedContentTypes} setSelectedContentTypes={setSelectedContentTypes} /> + + + + { + const appActions = await sdk.cma.appAction.getManyForEnvironment({ + environmentId: sdk.ids.environment, + spaceId: sdk.ids.space, + }); + const appAction = appActions.items.find((action) => action.name === actionName); + if (!appAction) { + throw new Error(`App action "${actionName}" not found`); + } + + return appAction.sys.id; +} + +export const createEntriesFromDocumentAction = async ( + sdk: PageAppSDK | ConfigAppSDK, + contentTypeIds: string[], + entries?: any[], // Optional: entries from plan to avoid re-analysis + documentJson?: unknown // Optional: document JSON if entries not provided (fallback) +) => { + try { + const appDefinitionId = sdk.ids.app; + + if (!appDefinitionId) { + throw new Error('App definition ID not found'); + } + + const appActionId = await getAppActionId(sdk, 'createEntriesFromDocumentAction'); + const result = await sdk.cma.appActionCall.createWithResult( + { + appDefinitionId, + appActionId, + }, + { + parameters: { + contentTypeIds, + ...(entries && entries.length > 0 ? { entries } : {}), + ...(documentJson && (!entries || entries.length === 0) ? { documentJson } : {}), + }, + } + ); + + if ('errors' in result && result.errors) { + throw new Error(JSON.stringify(result.errors)); + } + + return result; + } catch (error) { + console.error('Error creating entries from document', error); + throw new Error( + error instanceof Error ? error.message : 'Failed to create entries from document' + ); + } +}; + +export const createPlanFromDocumentAction = async ( + sdk: PageAppSDK | ConfigAppSDK, + contentTypeIds: string[], + documentJson: unknown +) => { + try { + const appDefinitionId = sdk.ids.app; + + if (!appDefinitionId) { + throw new Error('App definition ID not found'); + } + + const appActionId = await getAppActionId(sdk, 'createPlanFromDocumentAction'); + const result = await sdk.cma.appActionCall.createWithResult( + { + appDefinitionId, + appActionId, + }, + { + parameters: { contentTypeIds, documentJson }, + } + ); + + if ('errors' in result && result.errors) { + throw new Error(JSON.stringify(result.errors)); + } + + return result; + } catch (error) { + console.error('Error creating plan from document', error); + throw new Error(error instanceof Error ? error.message : 'Failed to create plan from document'); + } +}; diff --git a/apps/google-docs/src/utils/googleDriveUtils.ts b/apps/google-docs/src/utils/googleDriveUtils.ts new file mode 100644 index 0000000000..e89229fd86 --- /dev/null +++ b/apps/google-docs/src/utils/googleDriveUtils.ts @@ -0,0 +1,29 @@ +/** + * Fetches a Google Doc as JSON from the Google Docs API + * @param documentId - The Google Doc ID + * @param oauthToken - OAuth token for authentication + * @returns Promise resolving to the document JSON + */ +export async function fetchGoogleDocAsJson( + documentId: string, + oauthToken: string +): Promise { + const res = await fetch( + `https://docs.googleapis.com/v1/documents/${documentId}?includeTabsContent=true`, + { + headers: { + Authorization: `Bearer ${oauthToken}`, + }, + } + ); + + if (!res.ok) { + const errorText = await res.text(); + throw new Error( + `Failed to fetch document JSON: ${res.status} ${res.statusText} - ${errorText}` + ); + } + + const json = await res.json(); + return json; +} From 94f8d1e68a2b45d474442d7280a53e0b424eff37 Mon Sep 17 00:00:00 2001 From: david shibley Date: Fri, 19 Dec 2025 13:52:35 -0700 Subject: [PATCH 07/14] feat: plan step --- apps/google-docs/contentful-app-manifest.json | 14 +- .../functions/createEntriesFromDocument.ts | 8 +- .../functions/createPlanFromDocument.ts | 136 ------------------ .../createPreview/createPreviewHandler.ts | 106 ++++++++++++-- apps/google-docs/package.json | 12 +- .../src/hooks/usePlanGeneration.ts | 5 +- .../google-docs/src/utils/appFunctionUtils.ts | 6 +- 7 files changed, 115 insertions(+), 172 deletions(-) delete mode 100644 apps/google-docs/functions/createPlanFromDocument.ts diff --git a/apps/google-docs/contentful-app-manifest.json b/apps/google-docs/contentful-app-manifest.json index eda0208815..6294732d5c 100644 --- a/apps/google-docs/contentful-app-manifest.json +++ b/apps/google-docs/contentful-app-manifest.json @@ -38,14 +38,12 @@ ] }, { - "id": "analyzeContentTypes", - "name": "Analyze Content Types", - "description": "Analyzes content type structure and relationships using AI.", - "path": "functions/handlers/createPreview/createContentTypesAnalysisHandler.js", - "entryFile": "functions/handlers/createPreview/createContentTypesAnalysisHandler.ts", - "allowNetworks": [ - "https://api.openai.com" - ], + "id": "createEntries", + "name": "Create Entries", + "description": "Creates entries in Contentful", + "path": "functions/handlers/createEntries/createEntriesHandler.js", + "entryFile": "functions/handlers/createEntries/createEntriesHandler.ts", + "allowNetworks": [], "accepts": [ "appaction.call" ] diff --git a/apps/google-docs/functions/createEntriesFromDocument.ts b/apps/google-docs/functions/createEntriesFromDocument.ts index 1c98ccf22c..64778a3c79 100644 --- a/apps/google-docs/functions/createEntriesFromDocument.ts +++ b/apps/google-docs/functions/createEntriesFromDocument.ts @@ -4,11 +4,11 @@ import type { FunctionTypeEnum, AppActionRequest, } from '@contentful/node-apps-toolkit'; -import { analyzeDocumentWithAgent } from './agents/documentParserAgent/documentParser.agent'; import { fetchContentTypes } from './service/contentTypeService'; import { initContentfulManagementClient } from './service/initCMAClient'; -import { createEntries } from './service/entryService'; import { EntryToCreate } from './agents/documentParserAgent/schema'; +import { createPreviewWithAgent } from './agents/documentParserAgent/documentParser.agent'; +import { createEntriesFromPreview } from './service/entryService'; export type AppActionParameters = { contentTypeIds: string[]; @@ -53,7 +53,7 @@ export const handler: FunctionEventHandler< totalEntries = entriesToCreate.length; } else if (documentJson) { // Fallback: analyze document if entries not provided but documentJson is available - const aiDocumentResponse = await analyzeDocumentWithAgent({ + const aiDocumentResponse = await createPreviewWithAgent({ documentJson, openAiApiKey, contentTypes, @@ -66,7 +66,7 @@ export const handler: FunctionEventHandler< } // INTEG-3264: Create the entries in Contentful using the entry service - const creationResult = await createEntries(cma, entriesToCreate, { + const creationResult = await createEntriesFromPreview(cma, entriesToCreate, { spaceId: context.spaceId, environmentId: context.environmentId, contentTypes, diff --git a/apps/google-docs/functions/createPlanFromDocument.ts b/apps/google-docs/functions/createPlanFromDocument.ts deleted file mode 100644 index 018b5165b7..0000000000 --- a/apps/google-docs/functions/createPlanFromDocument.ts +++ /dev/null @@ -1,136 +0,0 @@ -import type { - FunctionEventContext, - FunctionEventHandler, - FunctionTypeEnum, - AppActionRequest, -} from '@contentful/node-apps-toolkit'; -import { analyzeDocumentWithAgent } from './agents/documentParserAgent/documentParser.agent'; -import { fetchContentTypes } from './service/contentTypeService'; -import { initContentfulManagementClient } from './service/initCMAClient'; -import { EntryToCreate } from './agents/documentParserAgent/schema'; -import { ContentTypeProps } from 'contentful-management'; - -export type AppActionParameters = { - contentTypeIds: string[]; - documentJson: unknown; // Google Doc JSON (fetched from frontend) -}; - -interface AppInstallationParameters { - openAiApiKey: string; -} - -interface AssetInfo { - url: string; - altText: string; - fileName: string; -} - -/** - * Extracts asset information from entries by scanning RichText fields for image tokens - */ -function extractAssetsFromEntries( - entries: EntryToCreate[], - contentTypes: ContentTypeProps[] -): AssetInfo[] { - const IMAGE_TOKEN_REGEX = /!\[([^\]]*?)\]\(([\s\S]*?)\)/g; - const assetsMap = new Map(); - - for (const entry of entries) { - const contentType = contentTypes.find((ct) => ct.sys.id === entry.contentTypeId); - if (!contentType) continue; - - // Check all RichText fields - for (const field of contentType.fields) { - if (field.type !== 'RichText') continue; - - const localizedValue = entry.fields[field.id]; - if (!localizedValue || typeof localizedValue !== 'object') continue; - - // Check all locales - for (const value of Object.values(localizedValue)) { - if (typeof value !== 'string') continue; - - // Reset regex state - IMAGE_TOKEN_REGEX.lastIndex = 0; - for (const match of value.matchAll(IMAGE_TOKEN_REGEX)) { - const altText = (match[1] || '').trim(); - const url = String(match[2]).replace(/\s+/g, '').trim(); - - if (!url) continue; - - // Extract filename from URL - let fileName = 'image'; - try { - const urlObj = new URL(url); - const pathname = urlObj.pathname.toLowerCase(); - const pathParts = pathname.split('/').filter(Boolean); - fileName = pathParts[pathParts.length - 1] || 'image'; - // Remove query params if present - fileName = fileName.split('?')[0]; - } catch { - // If URL parsing fails, use default - } - - // Use URL as key to avoid duplicates - if (!assetsMap.has(url)) { - assetsMap.set(url, { - url, - altText: altText || fileName, - fileName: fileName || 'image', - }); - } - } - } - } - } - - return Array.from(assetsMap.values()); -} - -/** - * App function handler that generates a plan (proposed entries) from a Google Doc - * without actually creating the entries. This allows users to review what will be created. - */ -export const handler: FunctionEventHandler< - FunctionTypeEnum.AppActionCall, - AppActionParameters -> = async ( - event: AppActionRequest<'Custom', AppActionParameters>, - context: FunctionEventContext -) => { - const { contentTypeIds, documentJson } = event.body; - const { openAiApiKey } = context.appInstallationParameters as AppInstallationParameters; - - if (!documentJson) { - throw new Error('Document JSON is required'); - } - - if (!contentTypeIds || contentTypeIds.length === 0) { - throw new Error('At least one content type ID is required'); - } - - const cma = initContentfulManagementClient(context); - const contentTypes = await fetchContentTypes(cma, new Set(contentTypeIds)); - console.log('Content types:', contentTypes); - - // Use the same AI agent to analyze the document and generate proposed entries - const aiDocumentResponse = await analyzeDocumentWithAgent({ - documentJson, - openAiApiKey, - contentTypes, - }); - console.log('AI document response:', aiDocumentResponse); - // Extract asset information from entries - const assets = extractAssetsFromEntries(aiDocumentResponse.entries, contentTypes); - - console.log('Assets:', assets); - // Return plan data without creating entries - return { - success: true, - response: { - ...aiDocumentResponse, - assets, - totalAssets: assets.length, - }, - }; -}; diff --git a/apps/google-docs/functions/handlers/createPreview/createPreviewHandler.ts b/apps/google-docs/functions/handlers/createPreview/createPreviewHandler.ts index 1aab6ee7c5..99140f4003 100644 --- a/apps/google-docs/functions/handlers/createPreview/createPreviewHandler.ts +++ b/apps/google-docs/functions/handlers/createPreview/createPreviewHandler.ts @@ -4,20 +4,89 @@ import type { FunctionTypeEnum, AppActionRequest, } from '@contentful/node-apps-toolkit'; +import { ContentTypeProps } from 'contentful-management'; import { createPreviewWithAgent } from '../../agents/documentParserAgent/documentParser.agent'; import { fetchContentTypes } from '../../service/contentTypeService'; import { initContentfulManagementClient } from '../../service/initCMAClient'; +import { EntryToCreate } from '../../agents/documentParserAgent/schema'; -export type CreatePreviewParameters = { +export type AppActionParameters = { contentTypeIds: string[]; - documentId: string; - oauthToken: string; + documentJson: unknown; // Google Doc JSON (fetched from frontend) }; interface AppInstallationParameters { openAiApiKey: string; } +interface AssetInfo { + url: string; + altText: string; + fileName: string; +} + +/** + * Extracts asset information from entries by scanning RichText fields for image tokens + */ +function extractAssetsFromEntries( + entries: EntryToCreate[], + contentTypes: ContentTypeProps[] +): AssetInfo[] { + const IMAGE_TOKEN_REGEX = /!\[([^\]]*?)\]\(([\s\S]*?)\)/g; + const assetsMap = new Map(); + + for (const entry of entries) { + const contentType = contentTypes.find((ct) => ct.sys.id === entry.contentTypeId); + if (!contentType) continue; + + // Check all RichText fields + for (const field of contentType.fields) { + if (field.type !== 'RichText') continue; + + const localizedValue = entry.fields[field.id]; + if (!localizedValue || typeof localizedValue !== 'object') continue; + + // Check all locales + for (const value of Object.values(localizedValue)) { + if (typeof value !== 'string') continue; + + // Reset regex state + IMAGE_TOKEN_REGEX.lastIndex = 0; + for (const match of value.matchAll(IMAGE_TOKEN_REGEX)) { + const altText = (match[1] || '').trim(); + const url = String(match[2]).replace(/\s+/g, '').trim(); + + if (!url) continue; + + // Extract filename from URL + let fileName = 'image'; + try { + const urlObj = new URL(url); + const pathname = urlObj.pathname.toLowerCase(); + const pathParts = pathname.split('/').filter(Boolean); + fileName = pathParts[pathParts.length - 1] || 'image'; + // Remove query params if present + fileName = fileName.split('?')[0]; + } catch { + // If URL parsing fails, use default + } + + // Use URL as key to avoid duplicates + if (!assetsMap.has(url)) { + assetsMap.set(url, { + url, + altText: altText || fileName, + fileName: fileName || 'image', + }); + } + } + } + } + } + + return Array.from(assetsMap.values()); +} + /** * Create Preview * @@ -27,34 +96,45 @@ interface AppInstallationParameters { */ export const handler: FunctionEventHandler< FunctionTypeEnum.AppActionCall, - CreatePreviewParameters + AppActionParameters > = async ( - event: AppActionRequest<'Custom', CreatePreviewParameters>, + event: AppActionRequest<'Custom', AppActionParameters>, context: FunctionEventContext ) => { - const { contentTypeIds, documentId, oauthToken } = event.body; + const { contentTypeIds, documentJson } = event.body; + console.log('Content types:', contentTypeIds); const { openAiApiKey } = context.appInstallationParameters as AppInstallationParameters; + console.log('Open AI API Key:', openAiApiKey); + if (!documentJson) { + throw new Error('Document JSON is required'); + } if (!contentTypeIds || contentTypeIds.length === 0) { throw new Error('At least one content type ID is required'); } const cma = initContentfulManagementClient(context); - const contentTypes = await fetchContentTypes(cma, new Set(contentTypeIds)); + console.log('Content types:', contentTypes); - // Process the document and create preview entries + // Use the same AI agent to analyze the document and generate proposed entries const aiDocumentResponse = await createPreviewWithAgent({ - documentId, - oauthToken, + documentJson, openAiApiKey, contentTypes, }); + console.log('AI document response:', aiDocumentResponse); + // Extract asset information from entries + const assets = extractAssetsFromEntries(aiDocumentResponse.entries, contentTypes); + console.log('Assets:', assets); + // Return plan data without creating entries return { success: true, - summary: aiDocumentResponse.summary, - totalEntriesExtracted: aiDocumentResponse.totalEntries, - entries: aiDocumentResponse.entries, + response: { + ...aiDocumentResponse, + assets, + totalAssets: assets.length, + }, }; }; diff --git a/apps/google-docs/package.json b/apps/google-docs/package.json index 62328404fa..8dc1b6181c 100644 --- a/apps/google-docs/package.json +++ b/apps/google-docs/package.json @@ -27,14 +27,12 @@ "build:frontend": "rm -rf build && tsc && vite build", "build:functions": "contentful-app-scripts build-functions --ci", "build": "rimraf build && npm run build:frontend && npm run build:functions", - "upload:app-dev": "contentful-app-scripts upload --bundle-dir ./build --organization-id 6xdLsz6lCsk0yPOccSsDK7 --definition-id 653vTnuQw3j5onU1tUoH6t --token $CONTENTFUL_ACCESS_TOKEN", - "deploy:sync-dev": "aws s3 sync ./build ${STATIC_S3_BASE}/google-docs-dev && aws cloudfront create-invalidation --distribution-id $GOOGLE_DOCS_TEST_CLOUDFRONT_DIST_ID --paths \"/*\" > /dev/null 2>&1", - "deploy:dev": "npm run build && npm run upload:app-dev && npm run deploy:sync-dev", + "upload:app-dev": "contentful-app-scripts upload --bundle-dir ./build --organization-id 6xdLsz6lCsk0yPOccSsDK7 --definition-id 7GZyBCU0FGVnR1mpRYJQ5L --token $CONTENTFUL_ACCESS_TOKEN" , + "deploy": "contentful-app-scripts upload --ci --bundle-dir ./build --organization-id 6B7UvF9RgdtICSBSvSIUMY --definition-id 1g8kRAvM4pb7Wb6pNPR87J --token ${CONTENTFUL_CMA_TOKEN}", + "deploy:oauth-dev": "aws s3 sync ./build s3://cf-apps-static-dev/apps/google-docs-dev && aws cloudfront create-invalidation --distribution-id E1V99AG4M4Z8HJ --paths \"/*\" > /dev/null 2>&1", + "deploy:dev": "npm run build && npm run upload:app-dev && npm run deploy:oauth-dev", "upload:app-staging": "contentful-app-scripts upload --bundle-dir ./build --organization-id 6xdLsz6lCsk0yPOccSsDK7 --definition-id 4i0mp5lQtgNsHYVrD5jIkj --token $CONTENTFUL_ACCESS_TOKEN", - "deploy:staging": "npm run build && npm run upload:app-staging && npm run deploy:sync-dev", - "deploy:sync-prod": "aws s3 sync ./build ${STATIC_S3_BASE}/google-docs && aws cloudfront create-invalidation --distribution-id $GOOGLE_DOCS_PROD_CLOUDFRONT_DIST_ID --paths \"/*\" > /dev/null 2>&1", - "upload": "contentful-app-scripts upload --bundle-dir ./build --organization-id 5EJGHo8tYJcjnEhYWDxivp --definition-id 3EaGZUMKRKVZUyrcoNJ4o4 --token $CONTENTFUL_ACCESS_TOKEN", - "deploy": "npm run build && npm run upload && npm run deploy:sync-prod" + "deploy:staging": "npm run build && npm run upload:app-staging && npm run deploy:oauth-dev" }, "eslintConfig": { "extends": "react-app" diff --git a/apps/google-docs/src/hooks/usePlanGeneration.ts b/apps/google-docs/src/hooks/usePlanGeneration.ts index 22559c4889..fa00e38c1d 100644 --- a/apps/google-docs/src/hooks/usePlanGeneration.ts +++ b/apps/google-docs/src/hooks/usePlanGeneration.ts @@ -1,6 +1,6 @@ import { useState, useCallback } from 'react'; import { PageAppSDK } from '@contentful/app-sdk'; -import { createPlanFromDocumentAction } from '../utils/appFunctionUtils'; +import { createPreviewFromDocumentAction } from '../utils/appFunctionUtils'; import { fetchGoogleDocAsJson } from '../utils/googleDriveUtils'; import { ERROR_MESSAGES } from '../constants/messages'; import { EntryToCreate } from '../../functions/agents/documentParserAgent/schema'; @@ -74,7 +74,8 @@ export const usePlanGeneration = ( // Fetch document JSON from frontend const documentJson = await fetchGoogleDocAsJson(documentId, oauthToken); - const response = await createPlanFromDocumentAction(sdk, contentTypeIds, documentJson); + console.log('Creating preview from document', documentJson, contentTypeIds); + const response = await createPreviewFromDocumentAction(sdk, contentTypeIds, documentJson); console.log('Plan generation response:', response); if (response.sys?.status === 'succeeded' && response.sys?.result) { diff --git a/apps/google-docs/src/utils/appFunctionUtils.ts b/apps/google-docs/src/utils/appFunctionUtils.ts index 6230522382..8115ff6ebe 100644 --- a/apps/google-docs/src/utils/appFunctionUtils.ts +++ b/apps/google-docs/src/utils/appFunctionUtils.ts @@ -64,7 +64,7 @@ export const createEntriesFromDocumentAction = async ( } }; -export const createPlanFromDocumentAction = async ( +export const createPreviewFromDocumentAction = async ( sdk: PageAppSDK | ConfigAppSDK, contentTypeIds: string[], documentJson: unknown @@ -76,7 +76,9 @@ export const createPlanFromDocumentAction = async ( throw new Error('App definition ID not found'); } - const appActionId = await getAppActionId(sdk, 'createPlanFromDocumentAction'); + console.log('Creating preview from document', documentJson, contentTypeIds); + + const appActionId = await getAppActionId(sdk, 'createPreview'); const result = await sdk.cma.appActionCall.createWithResult( { appDefinitionId, From 96044937f1785078ed917eb25ac890ddda5eb9b7 Mon Sep 17 00:00:00 2001 From: david shibley Date: Fri, 19 Dec 2025 14:04:49 -0700 Subject: [PATCH 08/14] fixing package.json --- apps/google-docs/package.json | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/google-docs/package.json b/apps/google-docs/package.json index 8dc1b6181c..adb962a29f 100644 --- a/apps/google-docs/package.json +++ b/apps/google-docs/package.json @@ -27,12 +27,13 @@ "build:frontend": "rm -rf build && tsc && vite build", "build:functions": "contentful-app-scripts build-functions --ci", "build": "rimraf build && npm run build:frontend && npm run build:functions", - "upload:app-dev": "contentful-app-scripts upload --bundle-dir ./build --organization-id 6xdLsz6lCsk0yPOccSsDK7 --definition-id 7GZyBCU0FGVnR1mpRYJQ5L --token $CONTENTFUL_ACCESS_TOKEN" , - "deploy": "contentful-app-scripts upload --ci --bundle-dir ./build --organization-id 6B7UvF9RgdtICSBSvSIUMY --definition-id 1g8kRAvM4pb7Wb6pNPR87J --token ${CONTENTFUL_CMA_TOKEN}", - "deploy:oauth-dev": "aws s3 sync ./build s3://cf-apps-static-dev/apps/google-docs-dev && aws cloudfront create-invalidation --distribution-id E1V99AG4M4Z8HJ --paths \"/*\" > /dev/null 2>&1", - "deploy:dev": "npm run build && npm run upload:app-dev && npm run deploy:oauth-dev", - "upload:app-staging": "contentful-app-scripts upload --bundle-dir ./build --organization-id 6xdLsz6lCsk0yPOccSsDK7 --definition-id 4i0mp5lQtgNsHYVrD5jIkj --token $CONTENTFUL_ACCESS_TOKEN", - "deploy:staging": "npm run build && npm run upload:app-staging && npm run deploy:oauth-dev" + "upload:app-dev": "contentful-app-scripts upload --bundle-dir ./build --organization-id 6xdLsz6lCsk0yPOccSsDK7 --definition-id 653vTnuQw3j5onU1tUoH6t --token $CONTENTFUL_ACCESS_TOKEN", + "deploy:sync-dev": "aws s3 sync ./build ${STATIC_S3_BASE}/google-docs-dev && aws cloudfront create-invalidation --distribution-id $GOOGLE_DOCS_TEST_CLOUDFRONT_DIST_ID --paths \"/*\" > /dev/null 2>&1", + "deploy:dev": "npm run build && npm run upload:app-dev && npm run deploy:sync-dev", + "deploy:staging": "npm run build && npm run upload:app-staging && npm run deploy:sync-dev", + "deploy:sync-prod": "aws s3 sync ./build ${STATIC_S3_BASE}/google-docs && aws cloudfront create-invalidation --distribution-id $GOOGLE_DOCS_PROD_CLOUDFRONT_DIST_ID --paths \"/*\" > /dev/null 2>&1", + "upload": "contentful-app-scripts upload --bundle-dir ./build --organization-id 5EJGHo8tYJcjnEhYWDxivp --definition-id 3EaGZUMKRKVZUyrcoNJ4o4 --token $CONTENTFUL_ACCESS_TOKEN", + "deploy": "npm run build && npm run upload && npm run deploy:sync-prod" }, "eslintConfig": { "extends": "react-app" From 004aa11d4d117c9554ea9e28a3aba01888bbd861 Mon Sep 17 00:00:00 2001 From: david shibley Date: Fri, 19 Dec 2025 14:20:40 -0700 Subject: [PATCH 09/14] moving logic to createEntriesHandler --- .../createEntries/createEntriesHandler.ts} | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) rename apps/google-docs/functions/{createEntriesFromDocument.ts => handlers/createEntries/createEntriesHandler.ts} (85%) diff --git a/apps/google-docs/functions/createEntriesFromDocument.ts b/apps/google-docs/functions/handlers/createEntries/createEntriesHandler.ts similarity index 85% rename from apps/google-docs/functions/createEntriesFromDocument.ts rename to apps/google-docs/functions/handlers/createEntries/createEntriesHandler.ts index 64778a3c79..4a93aac585 100644 --- a/apps/google-docs/functions/createEntriesFromDocument.ts +++ b/apps/google-docs/functions/handlers/createEntries/createEntriesHandler.ts @@ -4,17 +4,17 @@ import type { FunctionTypeEnum, AppActionRequest, } from '@contentful/node-apps-toolkit'; -import { fetchContentTypes } from './service/contentTypeService'; -import { initContentfulManagementClient } from './service/initCMAClient'; -import { EntryToCreate } from './agents/documentParserAgent/schema'; -import { createPreviewWithAgent } from './agents/documentParserAgent/documentParser.agent'; -import { createEntriesFromPreview } from './service/entryService'; +import { EntryToCreate } from '../../agents/documentParserAgent/schema'; +import { createEntriesFromPreview } from '../../service/entryService'; +import { initContentfulManagementClient } from '../../service/initCMAClient'; +import { fetchContentTypes } from '../../service/contentTypeService'; +import { createPreviewWithAgent } from '../../agents/documentParserAgent/documentParser.agent'; -export type AppActionParameters = { +interface CreateEntriesParameters { contentTypeIds: string[]; documentJson?: unknown; // Optional: Google Doc JSON (if entries not provided, will analyze document) entries?: EntryToCreate[]; // Optional: if provided, skip document analysis and use these entries -}; +} interface AppInstallationParameters { openAiApiKey: string; @@ -26,9 +26,9 @@ interface AppInstallationParameters { */ export const handler: FunctionEventHandler< FunctionTypeEnum.AppActionCall, - AppActionParameters + CreateEntriesParameters > = async ( - event: AppActionRequest<'Custom', AppActionParameters>, + event: AppActionRequest<'Custom', CreateEntriesParameters>, context: FunctionEventContext ) => { const { contentTypeIds, documentJson, entries: providedEntries } = event.body; From 6c505b70d5d5f4f0d7a65f6904f5c2a74833cb51 Mon Sep 17 00:00:00 2001 From: david shibley Date: Fri, 19 Dec 2025 15:31:07 -0700 Subject: [PATCH 10/14] responding to feedback, removing dead code, refactoring --- .../documentParser.agent.ts | 14 - .../createEntries/createEntriesHandler.ts | 13 +- .../createPreview/createPreviewHandler.ts | 84 +---- apps/google-docs/package.json | 1 + .../src/components/page/PreviewModal.tsx | 296 +++--------------- .../src/hooks/useDocumentSubmission.ts | 9 +- .../src/hooks/usePlanGeneration.ts | 156 --------- .../src/hooks/usePreviewGeneration.ts | 117 +++++++ apps/google-docs/src/locations/Page.tsx | 102 +++--- .../google-docs/src/utils/appFunctionUtils.ts | 6 +- 10 files changed, 222 insertions(+), 576 deletions(-) delete mode 100644 apps/google-docs/src/hooks/usePlanGeneration.ts create mode 100644 apps/google-docs/src/hooks/usePreviewGeneration.ts diff --git a/apps/google-docs/functions/agents/documentParserAgent/documentParser.agent.ts b/apps/google-docs/functions/agents/documentParserAgent/documentParser.agent.ts index 386384202c..2ff91d9970 100644 --- a/apps/google-docs/functions/agents/documentParserAgent/documentParser.agent.ts +++ b/apps/google-docs/functions/agents/documentParserAgent/documentParser.agent.ts @@ -44,15 +44,8 @@ export async function createPreviewWithAgent( apiKey: openAiApiKey, }); - console.log('Document Parser Agent document JSON received (fetched from frontend)'); - - // Step 1: Build extraction prompt - const promptStartTime = Date.now(); const prompt = buildExtractionPrompt({ contentTypes, documentJson, locale }); - const promptDuration = Date.now() - promptStartTime; - console.log(`[Timing] Build extraction prompt: ${promptDuration}ms`); - // Step 3: Generate object with AI const aiStartTime = Date.now(); const result = await generateObject({ model: openaiClient(modelVersion), @@ -64,14 +57,7 @@ export async function createPreviewWithAgent( const aiDuration = Date.now() - aiStartTime; console.log(`[Timing] AI generateObject: ${aiDuration}ms`); - // Step 4: Process result - const processStartTime = Date.now(); const finalResult = result.object as FinalEntriesResult; - const processDuration = Date.now() - processStartTime; - console.log(`[Timing] Process result: ${processDuration}ms`); - - const totalDuration = Date.now() - promptStartTime; - console.log(`[Timing] Total Document Parser Agent execution: ${totalDuration}ms`); return finalResult; } diff --git a/apps/google-docs/functions/handlers/createEntries/createEntriesHandler.ts b/apps/google-docs/functions/handlers/createEntries/createEntriesHandler.ts index 4a93aac585..990be7058e 100644 --- a/apps/google-docs/functions/handlers/createEntries/createEntriesHandler.ts +++ b/apps/google-docs/functions/handlers/createEntries/createEntriesHandler.ts @@ -31,8 +31,7 @@ export const handler: FunctionEventHandler< event: AppActionRequest<'Custom', CreateEntriesParameters>, context: FunctionEventContext ) => { - const { contentTypeIds, documentJson, entries: providedEntries } = event.body; - const { openAiApiKey } = context.appInstallationParameters as AppInstallationParameters; + const { contentTypeIds, entries: providedEntries } = event.body; if (!contentTypeIds || contentTypeIds.length === 0) { throw new Error('At least one content type ID is required'); @@ -51,16 +50,6 @@ export const handler: FunctionEventHandler< entriesToCreate = providedEntries; summary = `Creating ${entriesToCreate.length} entries from plan`; totalEntries = entriesToCreate.length; - } else if (documentJson) { - // Fallback: analyze document if entries not provided but documentJson is available - const aiDocumentResponse = await createPreviewWithAgent({ - documentJson, - openAiApiKey, - contentTypes, - }); - entriesToCreate = aiDocumentResponse.entries; - summary = aiDocumentResponse.summary; - totalEntries = aiDocumentResponse.totalEntries; } else { throw new Error('Either entries or documentJson must be provided'); } diff --git a/apps/google-docs/functions/handlers/createPreview/createPreviewHandler.ts b/apps/google-docs/functions/handlers/createPreview/createPreviewHandler.ts index 99140f4003..cef1c99b36 100644 --- a/apps/google-docs/functions/handlers/createPreview/createPreviewHandler.ts +++ b/apps/google-docs/functions/handlers/createPreview/createPreviewHandler.ts @@ -4,11 +4,9 @@ import type { FunctionTypeEnum, AppActionRequest, } from '@contentful/node-apps-toolkit'; -import { ContentTypeProps } from 'contentful-management'; import { createPreviewWithAgent } from '../../agents/documentParserAgent/documentParser.agent'; import { fetchContentTypes } from '../../service/contentTypeService'; import { initContentfulManagementClient } from '../../service/initCMAClient'; -import { EntryToCreate } from '../../agents/documentParserAgent/schema'; export type AppActionParameters = { contentTypeIds: string[]; @@ -19,74 +17,6 @@ interface AppInstallationParameters { openAiApiKey: string; } -interface AssetInfo { - url: string; - altText: string; - fileName: string; -} - -/** - * Extracts asset information from entries by scanning RichText fields for image tokens - */ -function extractAssetsFromEntries( - entries: EntryToCreate[], - contentTypes: ContentTypeProps[] -): AssetInfo[] { - const IMAGE_TOKEN_REGEX = /!\[([^\]]*?)\]\(([\s\S]*?)\)/g; - const assetsMap = new Map(); - - for (const entry of entries) { - const contentType = contentTypes.find((ct) => ct.sys.id === entry.contentTypeId); - if (!contentType) continue; - - // Check all RichText fields - for (const field of contentType.fields) { - if (field.type !== 'RichText') continue; - - const localizedValue = entry.fields[field.id]; - if (!localizedValue || typeof localizedValue !== 'object') continue; - - // Check all locales - for (const value of Object.values(localizedValue)) { - if (typeof value !== 'string') continue; - - // Reset regex state - IMAGE_TOKEN_REGEX.lastIndex = 0; - for (const match of value.matchAll(IMAGE_TOKEN_REGEX)) { - const altText = (match[1] || '').trim(); - const url = String(match[2]).replace(/\s+/g, '').trim(); - - if (!url) continue; - - // Extract filename from URL - let fileName = 'image'; - try { - const urlObj = new URL(url); - const pathname = urlObj.pathname.toLowerCase(); - const pathParts = pathname.split('/').filter(Boolean); - fileName = pathParts[pathParts.length - 1] || 'image'; - // Remove query params if present - fileName = fileName.split('?')[0]; - } catch { - // If URL parsing fails, use default - } - - // Use URL as key to avoid duplicates - if (!assetsMap.has(url)) { - assetsMap.set(url, { - url, - altText: altText || fileName, - fileName: fileName || 'image', - }); - } - } - } - } - } - - return Array.from(assetsMap.values()); -} - /** * Create Preview * @@ -102,9 +32,7 @@ export const handler: FunctionEventHandler< context: FunctionEventContext ) => { const { contentTypeIds, documentJson } = event.body; - console.log('Content types:', contentTypeIds); const { openAiApiKey } = context.appInstallationParameters as AppInstallationParameters; - console.log('Open AI API Key:', openAiApiKey); if (!documentJson) { throw new Error('Document JSON is required'); } @@ -115,7 +43,6 @@ export const handler: FunctionEventHandler< const cma = initContentfulManagementClient(context); const contentTypes = await fetchContentTypes(cma, new Set(contentTypeIds)); - console.log('Content types:', contentTypes); // Use the same AI agent to analyze the document and generate proposed entries const aiDocumentResponse = await createPreviewWithAgent({ @@ -123,18 +50,13 @@ export const handler: FunctionEventHandler< openAiApiKey, contentTypes, }); - console.log('AI document response:', aiDocumentResponse); - // Extract asset information from entries - const assets = extractAssetsFromEntries(aiDocumentResponse.entries, contentTypes); - console.log('Assets:', assets); - // Return plan data without creating entries return { success: true, response: { - ...aiDocumentResponse, - assets, - totalAssets: assets.length, + entries: aiDocumentResponse.entries, + summary: aiDocumentResponse.summary, + totalEntries: aiDocumentResponse.totalEntries, }, }; }; diff --git a/apps/google-docs/package.json b/apps/google-docs/package.json index adb962a29f..62328404fa 100644 --- a/apps/google-docs/package.json +++ b/apps/google-docs/package.json @@ -30,6 +30,7 @@ "upload:app-dev": "contentful-app-scripts upload --bundle-dir ./build --organization-id 6xdLsz6lCsk0yPOccSsDK7 --definition-id 653vTnuQw3j5onU1tUoH6t --token $CONTENTFUL_ACCESS_TOKEN", "deploy:sync-dev": "aws s3 sync ./build ${STATIC_S3_BASE}/google-docs-dev && aws cloudfront create-invalidation --distribution-id $GOOGLE_DOCS_TEST_CLOUDFRONT_DIST_ID --paths \"/*\" > /dev/null 2>&1", "deploy:dev": "npm run build && npm run upload:app-dev && npm run deploy:sync-dev", + "upload:app-staging": "contentful-app-scripts upload --bundle-dir ./build --organization-id 6xdLsz6lCsk0yPOccSsDK7 --definition-id 4i0mp5lQtgNsHYVrD5jIkj --token $CONTENTFUL_ACCESS_TOKEN", "deploy:staging": "npm run build && npm run upload:app-staging && npm run deploy:sync-dev", "deploy:sync-prod": "aws s3 sync ./build ${STATIC_S3_BASE}/google-docs && aws cloudfront create-invalidation --distribution-id $GOOGLE_DOCS_PROD_CLOUDFRONT_DIST_ID --paths \"/*\" > /dev/null 2>&1", "upload": "contentful-app-scripts upload --bundle-dir ./build --organization-id 5EJGHo8tYJcjnEhYWDxivp --definition-id 3EaGZUMKRKVZUyrcoNJ4o4 --token $CONTENTFUL_ACCESS_TOKEN", diff --git a/apps/google-docs/src/components/page/PreviewModal.tsx b/apps/google-docs/src/components/page/PreviewModal.tsx index 09106d34ca..5d32b02b15 100644 --- a/apps/google-docs/src/components/page/PreviewModal.tsx +++ b/apps/google-docs/src/components/page/PreviewModal.tsx @@ -1,138 +1,27 @@ -import { Button, Modal, Paragraph, Box, Heading, List, Flex } from '@contentful/f36-components'; +import { Button, Modal, Paragraph, Box, Flex } from '@contentful/f36-components'; import { EntryToCreate } from '../../../functions/agents/documentParserAgent/schema'; -interface AssetInfo { - url: string; - altText: string; - fileName: string; -} - -interface PlanData { +interface PreviewData { summary: string; totalEntries: number; entries: EntryToCreate[]; - assets?: AssetInfo[]; - totalAssets?: number; } -interface PlanReviewModalProps { +interface PreviewModalProps { isOpen: boolean; onClose: () => void; onCreateEntries: () => void; - plan: PlanData | null; + preview: PreviewData | null; isLoading: boolean; } -/** - * Extracts a display title from an entry by looking for common title field names - */ -function extractEntryTitle(entry: EntryToCreate, locale: string = 'en-US'): string { - if (!entry || !entry.fields) { - return 'Untitled Entry'; - } - - // Common field names that might contain titles - const titleFieldNames = ['title', 'name', 'heading', 'headline', 'label']; - - for (const fieldName of titleFieldNames) { - const field = entry.fields[fieldName]; - if (field && typeof field === 'object') { - const value = field[locale] || Object.values(field)[0]; - if (typeof value === 'string' && value.trim()) { - return value.trim(); - } - } - } - - // If no title field found, try to get first non-empty string field - for (const [fieldId, localizedValue] of Object.entries(entry.fields)) { - if (localizedValue && typeof localizedValue === 'object') { - const value = localizedValue[locale] || Object.values(localizedValue)[0]; - if (typeof value === 'string' && value.trim()) { - return value.trim(); - } - } - } - - // Fallback to content type ID - return `Entry (${entry.contentTypeId || 'unknown'})`; -} - -/** - * Builds a hierarchical structure from entries - * For now, we'll infer relationships based on content type patterns - * In the future, this could be enhanced with explicit dependency data from the AI - */ -interface EntryNode { - entry: EntryToCreate; - title: string; - children: EntryNode[]; - level: number; -} - -function buildEntryHierarchy(entries: EntryToCreate[]): EntryNode[] { - if (!entries || !Array.isArray(entries)) { - return []; - } - - const nodes: Array = entries - .map((entry) => { - if (!entry) { - return null; - } - const title = extractEntryTitle(entry); - return { - entry, - title, - children: [], - level: 0, - }; - }) - .filter((node) => node !== null); - - // Simple heuristic: group entries by content type patterns - // Articles/Pages are typically parents, Sections/FAQs are children - const parentTypes = ['article', 'page', 'blog', 'post']; - const childTypes = ['section', 'faq', 'item', 'block']; - - const parents: EntryNode[] = []; - const children: EntryNode[] = []; - - nodes.forEach((node) => { - if (!node) { - return; - } - const contentTypeLower = node.entry.contentTypeId.toLowerCase(); - const isParent = parentTypes.some((type) => contentTypeLower.includes(type)); - const isChild = childTypes.some((type) => contentTypeLower.includes(type)); - - if (isParent && !isChild) { - parents.push(node); - } else if (isChild) { - children.push(node); - } else { - // Default to parent if unclear - parents.push(node); - } - }); - - // If we have one parent and multiple children, attach children to parent - if (parents.length === 1 && children.length > 0) { - parents[0].children = children.map((child) => ({ ...child, level: 1 })); - return parents; - } - - // Otherwise return flat structure - return nodes.filter((node): node is EntryNode => node !== null); -} - export const PreviewModal = ({ isOpen, onClose, onCreateEntries, - plan, + preview, isLoading, -}: PlanReviewModalProps) => { +}: PreviewModalProps) => { const handleCreateEntries = () => { onCreateEntries(); }; @@ -143,136 +32,12 @@ export const PreviewModal = ({ } }; - if (!plan) { + if (!preview) { return null; } - const entries = plan.entries || []; - const totalEntries = plan.totalEntries ?? entries.length; - const assets = plan.assets || []; - const totalAssets = plan.totalAssets ?? assets.length; - - // Only build hierarchy if we have entries - const entryHierarchy = entries.length > 0 ? buildEntryHierarchy(entries) : []; - - const renderEntryNode = ( - node: EntryNode, - index: number, - isLast: boolean, - isChild: boolean = false, - parentHasMoreSiblings: boolean = false - ) => { - const hasChildren = node.children.length > 0; - const indent = node.level * 24; - - // Extract content type name from the title (text in parentheses) - const titleMatch = - node.title && typeof node.title === 'string' ? node.title.match(/^(.+?)\s*\((.+?)\)$/) : null; - const displayTitle = - titleMatch && titleMatch[1] ? titleMatch[1].trim() : node.title || 'Untitled'; - const contentTypeName = - titleMatch && titleMatch[2] ? titleMatch[2].trim() : node.entry?.contentTypeId || ''; - - return ( - - - {/* Vertical line for children - extends from parent */} - {isChild && ( - <> - {/* Vertical line */} - - {/* Horizontal connector line */} - - - )} - - {/* Entry card */} - - {/* Title with content type */} - - - {displayTitle.length > 40 ? displayTitle.substring(0, 40) + '...' : displayTitle} - - {contentTypeName && ( - - ({contentTypeName}) - - )} - - - - - {/* Render children */} - {hasChildren && ( - - {/* Vertical line connecting all children */} - - - {node.children.map((child, childIndex) => - renderEntryNode( - child, - childIndex, - childIndex === node.children.length - 1, - true, - childIndex < node.children.length - 1 - ) - )} - - - )} - - ); - }; + const entries = preview.entries || []; + const totalEntries = preview.totalEntries ?? entries.length; return ( - {plan.summary || + {preview.summary || `Based off the document, ${totalEntries} ${ totalEntries === 1 ? 'entry is' : 'entries are' } being suggested:`} - {entryHierarchy.length > 0 ? ( + {entries.length > 0 ? ( - {entryHierarchy.map((node, index) => - renderEntryNode(node, index, index === entryHierarchy.length - 1) - )} + {entries.map((entry, index) => { + const title = entry.fields.title?.['en-US'] || ''; + return ( + + + + + {title.length > 60 ? title.substring(0, 60) + '...' : title} + + + ({entry.contentTypeId || 'unknown'}) + + + + + ); + })} ) : ( No entries found diff --git a/apps/google-docs/src/hooks/useDocumentSubmission.ts b/apps/google-docs/src/hooks/useDocumentSubmission.ts index a76989730b..015fc119e6 100644 --- a/apps/google-docs/src/hooks/useDocumentSubmission.ts +++ b/apps/google-docs/src/hooks/useDocumentSubmission.ts @@ -1,6 +1,6 @@ import { useState, useCallback } from 'react'; import { PageAppSDK } from '@contentful/app-sdk'; -import { createEntriesFromDocumentAction } from '../utils/appFunctionUtils'; +import { createEntries } from '../utils/appFunctionUtils'; import { fetchGoogleDocAsJson } from '../utils/googleDriveUtils'; import { ERROR_MESSAGES, SUCCESS_MESSAGES } from '../constants/messages'; import { EntryToCreate } from '../../functions/agents/documentParserAgent/schema'; @@ -72,12 +72,7 @@ export const useDocumentSubmission = ( documentJson = await fetchGoogleDocAsJson(documentId, oauthToken); } - const response = await createEntriesFromDocumentAction( - sdk, - contentTypeIds, - entries, - documentJson - ) as any; + const response = (await createEntries(sdk, contentTypeIds, entries, documentJson)) as any; const result: FinalEntriesResult = response.sys.result; setPreviewEntries(result.entries); diff --git a/apps/google-docs/src/hooks/usePlanGeneration.ts b/apps/google-docs/src/hooks/usePlanGeneration.ts deleted file mode 100644 index fa00e38c1d..0000000000 --- a/apps/google-docs/src/hooks/usePlanGeneration.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { useState, useCallback } from 'react'; -import { PageAppSDK } from '@contentful/app-sdk'; -import { createPreviewFromDocumentAction } from '../utils/appFunctionUtils'; -import { fetchGoogleDocAsJson } from '../utils/googleDriveUtils'; -import { ERROR_MESSAGES } from '../constants/messages'; -import { EntryToCreate } from '../../functions/agents/documentParserAgent/schema'; - -interface AssetInfo { - url: string; - altText: string; - fileName: string; -} - -interface PlanData { - summary: string; - totalEntries: number; - entries: EntryToCreate[]; - assets?: AssetInfo[]; - totalAssets?: number; -} - -interface UsePlanGenerationReturn { - plan: PlanData | null; - isLoading: boolean; - error: string | null; - generatePlan: (contentTypeIds: string[]) => Promise; - clearPlan: () => void; -} - -export const usePlanGeneration = ( - sdk: PageAppSDK, - documentId: string, - oauthToken: string -): UsePlanGenerationReturn => { - const [plan, setPlan] = useState(null); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); - - const validatePlanGeneration = useCallback( - (contentTypeIds: string[]): string | null => { - const openAiApiKey = sdk.parameters.installation?.openAiApiKey as string | undefined; - - if (!openAiApiKey || !openAiApiKey.trim()) { - return ERROR_MESSAGES.NO_API_KEY; - } - - if (!documentId || !documentId.trim()) { - return ERROR_MESSAGES.NO_DOCUMENT; - } - - if (contentTypeIds.length === 0) { - return ERROR_MESSAGES.NO_CONTENT_TYPE; - } - - return null; - }, - [sdk, documentId] - ); - - const generatePlan = useCallback( - async (contentTypeIds: string[]) => { - const validationError = validatePlanGeneration(contentTypeIds); - if (validationError) { - setError(validationError); - setPlan(null); - return; - } - - setIsLoading(true); - setError(null); - setPlan(null); - - try { - // Fetch document JSON from frontend - const documentJson = await fetchGoogleDocAsJson(documentId, oauthToken); - - console.log('Creating preview from document', documentJson, contentTypeIds); - const response = await createPreviewFromDocumentAction(sdk, contentTypeIds, documentJson); - console.log('Plan generation response:', response); - - if (response.sys?.status === 'succeeded' && response.sys?.result) { - const result = response.sys.result as { - success?: boolean; - response?: { - summary: string; - totalEntries: number; - entries: EntryToCreate[]; - assets?: AssetInfo[]; - totalAssets?: number; - }; - summary?: string; - totalEntries?: number; - entries?: EntryToCreate[]; - assets?: AssetInfo[]; - totalAssets?: number; - }; - - // Handle both response structures: direct result or wrapped in response object - const planData = result.response || result; - - // Type guard to ensure planData has the required properties - if ( - planData && - typeof planData === 'object' && - 'summary' in planData && - 'totalEntries' in planData && - 'entries' in planData && - typeof planData.summary === 'string' && - typeof planData.totalEntries === 'number' && - Array.isArray(planData.entries) - ) { - // Safely extract assets and totalAssets with proper type checking - const assets = - 'assets' in planData && Array.isArray(planData.assets) - ? (planData.assets as AssetInfo[]) - : undefined; - const totalAssets = - 'totalAssets' in planData && typeof planData.totalAssets === 'number' - ? planData.totalAssets - : undefined; - - setPlan({ - summary: planData.summary, - totalEntries: planData.totalEntries, - entries: planData.entries, - assets, - totalAssets, - }); - } else { - setError('Invalid plan data structure received'); - } - } else { - setError('Failed to generate plan'); - } - } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to generate plan'); - } finally { - setIsLoading(false); - } - }, - [sdk, documentId, oauthToken, validatePlanGeneration] - ); - - const clearPlan = useCallback(() => { - setPlan(null); - setError(null); - }, []); - - return { - plan, - isLoading, - error, - generatePlan, - clearPlan, - }; -}; diff --git a/apps/google-docs/src/hooks/usePreviewGeneration.ts b/apps/google-docs/src/hooks/usePreviewGeneration.ts new file mode 100644 index 0000000000..0d5c271f05 --- /dev/null +++ b/apps/google-docs/src/hooks/usePreviewGeneration.ts @@ -0,0 +1,117 @@ +import { useState, useCallback } from 'react'; +import { PageAppSDK } from '@contentful/app-sdk'; +import { createPreview } from '../utils/appFunctionUtils'; +import { fetchGoogleDocAsJson } from '../utils/googleDriveUtils'; +import { EntryToCreate } from '../../functions/agents/documentParserAgent/schema'; + +interface AssetInfo { + url: string; + altText: string; + fileName: string; +} + +interface PreviewData { + summary: string; + totalEntries: number; + entries: EntryToCreate[]; + assets?: AssetInfo[]; + totalAssets?: number; +} + +interface UsePreviewGenerationReturn { + preview: PreviewData | null; + isLoading: boolean; + error: string | null; + generatePreview: (contentTypeIds: string[]) => Promise; +} + +export const usePreviewGeneration = ( + sdk: PageAppSDK, + documentId: string, + oauthToken: string +): UsePreviewGenerationReturn => { + const [preview, setPreview] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const generatePreview = useCallback( + async (contentTypeIds: string[]) => { + setIsLoading(true); + setError(null); + setPreview(null); + + try { + const documentJson = await fetchGoogleDocAsJson(documentId, oauthToken); + + const response = await createPreview(sdk, contentTypeIds, documentJson); + + if (response.sys?.status === 'succeeded' && response.sys?.result) { + const result = response.sys.result as { + success?: boolean; + response?: { + summary: string; + totalEntries: number; + entries: EntryToCreate[]; + assets?: AssetInfo[]; + totalAssets?: number; + }; + summary?: string; + totalEntries?: number; + entries?: EntryToCreate[]; + assets?: AssetInfo[]; + totalAssets?: number; + }; + + // Handle both response structures: direct result or wrapped in response object + const previewData = result.response || result; + + // Type guard to ensure previewData has the required properties + if ( + previewData && + typeof previewData === 'object' && + 'summary' in previewData && + 'totalEntries' in previewData && + 'entries' in previewData && + typeof previewData.summary === 'string' && + typeof previewData.totalEntries === 'number' && + Array.isArray(previewData.entries) + ) { + // Safely extract assets and totalAssets with proper type checking + const assets = + 'assets' in previewData && Array.isArray(previewData.assets) + ? (previewData.assets as AssetInfo[]) + : undefined; + const totalAssets = + 'totalAssets' in previewData && typeof previewData.totalAssets === 'number' + ? previewData.totalAssets + : undefined; + + setPreview({ + summary: previewData.summary, + totalEntries: previewData.totalEntries, + entries: previewData.entries, + assets, + totalAssets, + }); + } else { + setError('Invalid preview data structure received'); + } + } else { + setError('Failed to generate preview'); + } + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to generate plan'); + } finally { + setIsLoading(false); + } + }, + [sdk, documentId, oauthToken] + ); + + return { + preview, + isLoading, + error, + generatePreview, + }; +}; diff --git a/apps/google-docs/src/locations/Page.tsx b/apps/google-docs/src/locations/Page.tsx index 3c92704d5a..cb03371254 100644 --- a/apps/google-docs/src/locations/Page.tsx +++ b/apps/google-docs/src/locations/Page.tsx @@ -12,13 +12,18 @@ import { LoadingModal } from '../components/page/LoadingModal'; import { useModalManagement, ModalType } from '../hooks/useModalManagement'; import { useProgressTracking } from '../hooks/useProgressTracking'; import { useDocumentSubmission } from '../hooks/useDocumentSubmission'; -import { usePlanGeneration } from '../hooks/usePlanGeneration'; +import { usePreviewGeneration } from '../hooks/usePreviewGeneration'; import SelectDocumentModal from '../components/page/SelectDocumentModal'; import { ViewPreviewModal } from '../components/page/ViewPreviewModal'; import { ReviewEntriesModal } from '../components/page/ReviewEntriesModal'; import { ErrorEntriesModal } from '../components/page/ErrorEntriesModal'; import { createEntriesFromPreview, EntryCreationResult } from '../services/entryService'; +interface EntryToCreate { + contentTypeId: string; + fields: Record>; +} + const Page = () => { const sdk = useSDK(); const { modalStates, openModal, closeModal } = useModalManagement(); @@ -38,12 +43,11 @@ const Page = () => { const { previewEntries, successMessage, errorMessage, submit, clearMessages, isSubmitting } = useDocumentSubmission(sdk, documentId, oauthToken); const { - plan, + preview, isLoading: isGeneratingPlan, error: planError, - generatePlan, - clearPlan, - } = usePlanGeneration(sdk, documentId, oauthToken); + generatePreview: generatePlan, + } = usePreviewGeneration(sdk, documentId, oauthToken); // Track previous submission state to detect completion const prevIsSubmittingRef = useRef(false); @@ -63,7 +67,6 @@ const Page = () => { closeModal(ModalType.PREVIEW); closeModal(ModalType.LOADING); clearMessages(); - clearPlan(); }; const handleUploadModalCloseRequest = (docId?: string) => { @@ -175,81 +178,72 @@ const Page = () => { const handlePlanReviewCancel = () => { closeModal(ModalType.PREVIEW); openModal(ModalType.CONTENT_TYPE_PICKER); - clearPlan(); }; - const handlePlanReviewCreateEntries = async () => { - if (!plan) return; + const handlePreviewModal = async () => { + if (!preview) return; - const contentTypeIds = plan.entries.map((entry) => entry.contentTypeId); + const contentTypeIds: string[] = preview.entries.map( + (entry: EntryToCreate) => entry.contentTypeId + ); const uniqueContentTypeIds = Array.from(new Set(contentTypeIds)); closeModal(ModalType.PREVIEW); openModal(ModalType.LOADING); // Create entries using the plan data - pass entries to avoid re-analysis - await submit(uniqueContentTypeIds, plan.entries); + await submit(uniqueContentTypeIds, preview.entries); }; - // Close the Loading modal when submission completes - useEffect(() => { - const submissionJustCompleted = prevIsSubmittingRef.current && !isSubmitting; - - if (submissionJustCompleted && modalStates.isLoadingModalOpen) { - closeModal(ModalType.LOADING); - clearPlan(); - } - - prevIsSubmittingRef.current = isSubmitting; - }, [isSubmitting, modalStates.isLoadingModalOpen, closeModal, clearPlan]); + // Track previous plan generation state to detect completion + const prevIsGeneratingPlanRef = useRef(false); // Handle plan generation completion useEffect(() => { - if (!isGeneratingPlan && plan && !planError) { + const planGenerationJustCompleted = prevIsGeneratingPlanRef.current && !isGeneratingPlan; + + if (planGenerationJustCompleted && modalStates.isLoadingModalOpen) { // Close loading modal first - if (modalStates.isLoadingModalOpen) { - closeModal(ModalType.LOADING); - } + closeModal(ModalType.LOADING); - // Validate plan has required data before opening modal - if (plan.entries && Array.isArray(plan.entries) && plan.summary) { - // Plan generation completed successfully, open plan review modal - if (!modalStates.isPreviewModalOpen && !modalStates.isContentTypePickerOpen) { + if (preview && !planError) { + // Plan generation completed successfully, open preview modal + if (preview.entries && Array.isArray(preview.entries) && preview.summary) { openModal(ModalType.PREVIEW); + } else { + // Invalid preview data + sdk.notifier.error('Invalid preview data received'); + openModal(ModalType.CONTENT_TYPE_PICKER); } - } else { - // Invalid plan data - sdk.notifier.error('Invalid plan data received'); - if (modalStates.isPreviewModalOpen) { - closeModal(ModalType.PREVIEW); - } - openModal(ModalType.CONTENT_TYPE_PICKER); - } - } else if (!isGeneratingPlan && planError) { - // Plan generation failed, close loading modal and return to content type picker - if (modalStates.isLoadingModalOpen) { - closeModal(ModalType.LOADING); - } - sdk.notifier.error(`Failed to generate plan: ${planError}`); - if (modalStates.isPreviewModalOpen) { - closeModal(ModalType.PREVIEW); - } - if (!modalStates.isContentTypePickerOpen) { + } else if (planError) { + // Plan generation failed, show error and return to content type picker + sdk.notifier.error(`Failed to generate preview: ${planError}`); openModal(ModalType.CONTENT_TYPE_PICKER); } } + + prevIsGeneratingPlanRef.current = isGeneratingPlan; }, [ isGeneratingPlan, - plan, + preview, planError, modalStates.isLoadingModalOpen, - modalStates.isPreviewModalOpen, - modalStates.isContentTypePickerOpen, - openModal, closeModal, + openModal, sdk, ]); + // Close the Loading modal when submission completes + useEffect(() => { + const submissionJustCompleted = prevIsSubmittingRef.current && !isSubmitting; + + if (submissionJustCompleted && modalStates.isLoadingModalOpen) { + closeModal(ModalType.LOADING); + } + + prevIsSubmittingRef.current = isSubmitting; + }, [isSubmitting, modalStates.isLoadingModalOpen, closeModal]); + return ( <> { diff --git a/apps/google-docs/src/utils/appFunctionUtils.ts b/apps/google-docs/src/utils/appFunctionUtils.ts index 8115ff6ebe..fcd4322269 100644 --- a/apps/google-docs/src/utils/appFunctionUtils.ts +++ b/apps/google-docs/src/utils/appFunctionUtils.ts @@ -23,7 +23,7 @@ export async function getAppActionId( return appAction.sys.id; } -export const createEntriesFromDocumentAction = async ( +export const createEntries = async ( sdk: PageAppSDK | ConfigAppSDK, contentTypeIds: string[], entries?: any[], // Optional: entries from plan to avoid re-analysis @@ -36,7 +36,7 @@ export const createEntriesFromDocumentAction = async ( throw new Error('App definition ID not found'); } - const appActionId = await getAppActionId(sdk, 'createEntriesFromDocumentAction'); + const appActionId = await getAppActionId(sdk, 'createEntries'); const result = await sdk.cma.appActionCall.createWithResult( { appDefinitionId, @@ -64,7 +64,7 @@ export const createEntriesFromDocumentAction = async ( } }; -export const createPreviewFromDocumentAction = async ( +export const createPreview = async ( sdk: PageAppSDK | ConfigAppSDK, contentTypeIds: string[], documentJson: unknown From ded35d1d1e0cb95d38117a81f7be019c0dc89e96 Mon Sep 17 00:00:00 2001 From: david shibley Date: Fri, 19 Dec 2025 15:39:53 -0700 Subject: [PATCH 11/14] removing appActionUtils --- apps/google-docs/src/utils/appActionUtils.ts | 102 ------------------- 1 file changed, 102 deletions(-) delete mode 100644 apps/google-docs/src/utils/appActionUtils.ts diff --git a/apps/google-docs/src/utils/appActionUtils.ts b/apps/google-docs/src/utils/appActionUtils.ts deleted file mode 100644 index 25bbfee32e..0000000000 --- a/apps/google-docs/src/utils/appActionUtils.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { PageAppSDK, ConfigAppSDK } from '@contentful/app-sdk'; - -/** - * Fetches the app action ID by name from the current environment - * @param sdk - The Contentful SDK instance - * @param actionName - The name of the app action to find - * @returns The app action ID - * @throws Error if the app action is not found - */ -export async function getAppActionId( - sdk: PageAppSDK | ConfigAppSDK, - actionName: string -): Promise { - const appActions = await sdk.cma.appAction.getManyForEnvironment({ - environmentId: sdk.ids.environment, - spaceId: sdk.ids.space, - }); - const appAction = appActions.items.find((action) => action.name === actionName); - if (!appAction) { - throw new Error(`App action "${actionName}" not found`); - } - - return appAction.sys.id; -} - -/** - * Generic helper to call an app action with parameters - * @param sdk - The Contentful SDK instance - * @param actionName - The name of the app action to call - * @param parameters - The parameters to pass to the app action - * @returns The result from the app action - * @throws Error if the app action fails - */ -async function callAppAction( - sdk: PageAppSDK | ConfigAppSDK, - actionName: string, - parameters: Record -): Promise { - try { - const appDefinitionId = sdk.ids.app; - - if (!appDefinitionId) { - throw new Error('App definition ID not found'); - } - - const appActionId = await getAppActionId(sdk, actionName); - const result = await sdk.cma.appActionCall.createWithResult( - { - appDefinitionId, - appActionId, - }, - { - parameters, - } - ); - - if ('errors' in result && result.errors) { - throw new Error(JSON.stringify(result.errors)); - } - - return result as T; - } catch (error) { - console.error(`Error calling app action "${actionName}"`, error); - throw new Error( - error instanceof Error ? error.message : `Failed to call app action "${actionName}"` - ); - } -} - -/** - * Analyzes content type structure and relationships using AI - * @param sdk - The Contentful SDK instance - * @param contentTypeIds - Array of content type IDs to analyze - * @returns Analysis result from the app action - */ -export const analyzeContentTypesAction = async ( - sdk: PageAppSDK | ConfigAppSDK, - contentTypeIds: string[], - oauthToken: string -) => { - return callAppAction(sdk, 'analyzeContentTypes', { contentTypeIds, oauthToken }); -}; - -/** - * Processes a document and creates Contentful entries - * @param sdk - The Contentful SDK instance - * @param contentTypeIds - Array of content type IDs to use for entry creation - * @param document - The document to process (JSON object or string) - * @returns Processing result from the app action - */ -export const createPreviewAction = async ( - sdk: PageAppSDK | ConfigAppSDK, - contentTypeIds: string[], - documentId: string, - oauthToken: string -) => { - return callAppAction(sdk, 'createPreview', { - contentTypeIds, - documentId, - oauthToken, - }); -}; From 8b123b4596d71a9b23b9dc83ea654699422e7ea6 Mon Sep 17 00:00:00 2001 From: david shibley Date: Fri, 19 Dec 2025 15:43:21 -0700 Subject: [PATCH 12/14] removing ViewPreviewModal --- .../src/components/page/ViewPreviewModal.tsx | 180 ------------------ apps/google-docs/src/locations/Page.tsx | 10 - 2 files changed, 190 deletions(-) delete mode 100644 apps/google-docs/src/components/page/ViewPreviewModal.tsx diff --git a/apps/google-docs/src/components/page/ViewPreviewModal.tsx b/apps/google-docs/src/components/page/ViewPreviewModal.tsx deleted file mode 100644 index 897b5842f2..0000000000 --- a/apps/google-docs/src/components/page/ViewPreviewModal.tsx +++ /dev/null @@ -1,180 +0,0 @@ -import React, { useMemo } from 'react'; -import { - Box, - Button, - Card, - Flex, - Heading, - Modal, - Paragraph, - Table, - Text, - Badge, -} from '@contentful/f36-components'; -import tokens from '@contentful/f36-tokens'; -import { EntryToCreate } from '../../../functions/agents/documentParserAgent/schema'; - -interface ViewPreviewModalProps { - isOpen: boolean; - onClose: () => void; - entries: EntryToCreate[] | null; - onConfirm: () => void; - isSubmitting: boolean; -} - -export const ViewPreviewModal: React.FC = ({ - isOpen, - onClose, - entries, - onConfirm, - isSubmitting, -}) => { - const entriesByContentType = useMemo(() => { - if (!entries) return {}; - - return entries.reduce((acc, entry) => { - if (!acc[entry.contentTypeId]) { - acc[entry.contentTypeId] = []; - } - acc[entry.contentTypeId].push(entry); - return acc; - }, {} as Record); - }, [entries]); - - const totalEntries = useMemo(() => entries?.length || 0, [entries]); - - const renderFieldValue = (value: any, maxLength: number = 100): string => { - if (value === null || value === undefined) { - return '—'; - } - - if (typeof value === 'object') { - const stringified = JSON.stringify(value, null, 2); - return stringified.length > maxLength - ? stringified.substring(0, maxLength) + '...' - : stringified; - } - - const stringValue = String(value); - return stringValue.length > maxLength - ? stringValue.substring(0, maxLength) + '...' - : stringValue; - }; - - const handleClose = () => { - if (isSubmitting) return; - onClose(); - }; - - const handleConfirm = () => { - if (isSubmitting) return; - onConfirm(); - }; - - if (!entries || entries.length === 0) { - return null; - } - - return ( - - {() => ( - <> - - - - - - - Based off the document, the following entries are being suggested: - - - - - {/* Entries by Content Type */} - {Object.entries(entriesByContentType).map(([contentTypeId, entries], ctIndex) => ( - - - - - {contentTypeId} - - {entries.length} entries - - - {entries.map((entry, entryIndex) => ( - - - - Entry {entryIndex + 1} - - - - - - - Field - Locale - Value - - - - {Object.entries(entry.fields).map(([fieldId, localizedValue]) => { - // localizedValue is a record like { 'en-US': actualValue } - return Object.entries(localizedValue).map(([locale, value]) => ( - - - {fieldId} - - - - {locale} - - - - - {renderFieldValue(value)} - - - - )); - })} - -
-
-
-
- ))} -
-
- ))} -
-
- - - - - - )} -
- ); -}; diff --git a/apps/google-docs/src/locations/Page.tsx b/apps/google-docs/src/locations/Page.tsx index cb03371254..85a3a44902 100644 --- a/apps/google-docs/src/locations/Page.tsx +++ b/apps/google-docs/src/locations/Page.tsx @@ -14,7 +14,6 @@ import { useProgressTracking } from '../hooks/useProgressTracking'; import { useDocumentSubmission } from '../hooks/useDocumentSubmission'; import { usePreviewGeneration } from '../hooks/usePreviewGeneration'; import SelectDocumentModal from '../components/page/SelectDocumentModal'; -import { ViewPreviewModal } from '../components/page/ViewPreviewModal'; import { ReviewEntriesModal } from '../components/page/ReviewEntriesModal'; import { ErrorEntriesModal } from '../components/page/ErrorEntriesModal'; import { createEntriesFromPreview, EntryCreationResult } from '../services/entryService'; @@ -174,7 +173,6 @@ const Page = () => { resetProgress(); }; - const handlePlanReviewCancel = () => { closeModal(ModalType.PREVIEW); openModal(ModalType.CONTENT_TYPE_PICKER); @@ -285,14 +283,6 @@ const Page = () => { onCancel={handleKeepCreating} /> - closeModal(ModalType.PREVIEW)} - entries={previewEntries} - onConfirm={() => handlePreviewModalConfirm(selectedContentTypes)} - isSubmitting={isCreatingEntries} - /> - closeModal(ModalType.REVIEW)} From c238afd35919a46acfce58935f735793dc50de3c Mon Sep 17 00:00:00 2001 From: david shibley Date: Fri, 19 Dec 2025 16:18:43 -0700 Subject: [PATCH 13/14] removing create entries --- apps/google-docs/contentful-app-manifest.json | 11 --- .../createEntries/createEntriesHandler.ts | 81 ------------------- .../src/components/page/PreviewModal.tsx | 26 +++--- .../src/hooks/useModalManagement.ts | 2 + apps/google-docs/src/locations/Page.tsx | 72 ++++++++--------- 5 files changed, 50 insertions(+), 142 deletions(-) delete mode 100644 apps/google-docs/functions/handlers/createEntries/createEntriesHandler.ts diff --git a/apps/google-docs/contentful-app-manifest.json b/apps/google-docs/contentful-app-manifest.json index 6294732d5c..33ee3494dd 100644 --- a/apps/google-docs/contentful-app-manifest.json +++ b/apps/google-docs/contentful-app-manifest.json @@ -37,17 +37,6 @@ "appaction.call" ] }, - { - "id": "createEntries", - "name": "Create Entries", - "description": "Creates entries in Contentful", - "path": "functions/handlers/createEntries/createEntriesHandler.js", - "entryFile": "functions/handlers/createEntries/createEntriesHandler.ts", - "allowNetworks": [], - "accepts": [ - "appaction.call" - ] - }, { "id": "initiateGdocOauth", "name": "Initiate Gdoc OAuth Flow", diff --git a/apps/google-docs/functions/handlers/createEntries/createEntriesHandler.ts b/apps/google-docs/functions/handlers/createEntries/createEntriesHandler.ts deleted file mode 100644 index 990be7058e..0000000000 --- a/apps/google-docs/functions/handlers/createEntries/createEntriesHandler.ts +++ /dev/null @@ -1,81 +0,0 @@ -import type { - FunctionEventContext, - FunctionEventHandler, - FunctionTypeEnum, - AppActionRequest, -} from '@contentful/node-apps-toolkit'; -import { EntryToCreate } from '../../agents/documentParserAgent/schema'; -import { createEntriesFromPreview } from '../../service/entryService'; -import { initContentfulManagementClient } from '../../service/initCMAClient'; -import { fetchContentTypes } from '../../service/contentTypeService'; -import { createPreviewWithAgent } from '../../agents/documentParserAgent/documentParser.agent'; - -interface CreateEntriesParameters { - contentTypeIds: string[]; - documentJson?: unknown; // Optional: Google Doc JSON (if entries not provided, will analyze document) - entries?: EntryToCreate[]; // Optional: if provided, skip document analysis and use these entries -} - -interface AppInstallationParameters { - openAiApiKey: string; -} - -/* - * Important Caveat: App Functions have a 30 second limit on execution time. - * There is a likely future where we will need to break down the function into smaller functions. - */ -export const handler: FunctionEventHandler< - FunctionTypeEnum.AppActionCall, - CreateEntriesParameters -> = async ( - event: AppActionRequest<'Custom', CreateEntriesParameters>, - context: FunctionEventContext -) => { - const { contentTypeIds, entries: providedEntries } = event.body; - - if (!contentTypeIds || contentTypeIds.length === 0) { - throw new Error('At least one content type ID is required'); - } - - const cma = initContentfulManagementClient(context); - const contentTypes = await fetchContentTypes(cma, new Set(contentTypeIds)); - - // If entries are provided (from plan), use them directly instead of re-analyzing - let entriesToCreate: EntryToCreate[]; - let summary: string; - let totalEntries: number; - - if (providedEntries && Array.isArray(providedEntries) && providedEntries.length > 0) { - // Use provided entries from plan - skip document analysis - entriesToCreate = providedEntries; - summary = `Creating ${entriesToCreate.length} entries from plan`; - totalEntries = entriesToCreate.length; - } else { - throw new Error('Either entries or documentJson must be provided'); - } - - // INTEG-3264: Create the entries in Contentful using the entry service - const creationResult = await createEntriesFromPreview(cma, entriesToCreate, { - spaceId: context.spaceId, - environmentId: context.environmentId, - contentTypes, - }); - console.log('Created Entries Result:', creationResult); - - // INTEG-3265: Create the assets in Contentful using the asset service - // await createAssets() - - return { - success: true, - response: { - summary, - totalEntriesExtracted: totalEntries, - createdEntries: creationResult.createdEntries.map((entry) => ({ - id: entry.sys.id, - contentType: entry.sys.contentType.sys.id, - })), - errors: creationResult.errors, - successRate: `${creationResult.createdEntries.length}/${totalEntries}`, - }, - }; -}; diff --git a/apps/google-docs/src/components/page/PreviewModal.tsx b/apps/google-docs/src/components/page/PreviewModal.tsx index 5d32b02b15..8c4b235a80 100644 --- a/apps/google-docs/src/components/page/PreviewModal.tsx +++ b/apps/google-docs/src/components/page/PreviewModal.tsx @@ -1,5 +1,6 @@ import { Button, Modal, Paragraph, Box, Flex } from '@contentful/f36-components'; import { EntryToCreate } from '../../../functions/agents/documentParserAgent/schema'; +import { SelectedContentType } from './ContentTypePickerModal'; interface PreviewData { summary: string; @@ -10,24 +11,22 @@ interface PreviewData { interface PreviewModalProps { isOpen: boolean; onClose: () => void; - onCreateEntries: () => void; + onCreateEntries: (contentTypes: SelectedContentType[]) => void; preview: PreviewData | null; + isCreatingEntries: boolean; isLoading: boolean; } export const PreviewModal = ({ isOpen, + isCreatingEntries, onClose, onCreateEntries, preview, isLoading, }: PreviewModalProps) => { - const handleCreateEntries = () => { - onCreateEntries(); - }; - const handleClose = () => { - if (!isLoading) { + if (!isLoading && !isCreatingEntries) { onClose(); } }; @@ -45,8 +44,8 @@ export const PreviewModal = ({ isShown={isOpen} onClose={handleClose} size="large" - shouldCloseOnOverlayClick={!isLoading} - shouldCloseOnEscapePress={!isLoading}> + shouldCloseOnOverlayClick={!isLoading && !isCreatingEntries} + shouldCloseOnEscapePress={!isLoading && !isCreatingEntries}> {() => ( <> @@ -104,11 +103,18 @@ export const PreviewModal = ({
-