diff --git a/docs/docs/cmd/spo/page/page-set.mdx b/docs/docs/cmd/spo/page/page-set.mdx
index ba3b36a6604..fc0fbd4081d 100644
--- a/docs/docs/cmd/spo/page/page-set.mdx
+++ b/docs/docs/cmd/spo/page/page-set.mdx
@@ -42,6 +42,9 @@ m365 spo page set [options]
`--title [title]`
: The title to set for the page.
+
+`--content [content]`
+: JSON string containing page content
```
Hello World
\"}]' +``` + +Note, how the content option has escaped double quotes `'[{\"controlType\": 4,\"id\": \"42b8afe8-dafe-4c11-bfbf-df5ef5b1feb7\",\"position\": {\"layoutIndex\": 1,\"zoneIndex\": 1,\"sectionIndex\": 1,\"sectionFactor\": 12,\"controlIndex\": 1},\"addedFromPersistedData\": true,\"innerHTML\":\"Hello World
\"}]'` compared to execution from bash `''[{"controlType": 4,"id": "42b8afe8-dafe-4c11-bfbf-df5ef5b1feb7","position": {"layoutIndex": 1,"zoneIndex": 1,"sectionIndex": 1,"sectionFactor": 12,"controlIndex": 1},"addedFromPersistedData": true,"innerHTML":"Hello World
"}]'`. + +:::caution Escaping JSON in PowerShell + + When using the `--content` option it's possible to enter a JSON string. In PowerShell 5 to 7.2 [specific escaping rules](./../../../user-guide/using-cli.mdx#escaping-double-quotes-in-powershell) apply due to an issue. Remember that you can also use [file tokens](./../../../user-guide/using-cli.mdx#passing-complex-content-into-cli-options) instead. + +::: + ## Examples Change the layout of the existing page to _Article_ @@ -102,6 +119,12 @@ Set page description m365 spo page set --name page.aspx --webUrl https://contoso.sharepoint.com/sites/a-team --description "Description to add for the page" ``` +Set page content + +```sh +m365 spo page set --name page.aspx --webUrl https://contoso.sharepoint.com/sites/a-team --content '[{\"controlType\": 4,\"id\": \"42b8afe8-dafe-4c11-bfbf-df5ef5b1feb7\",\"position\": {\"layoutIndex\": 1,\"zoneIndex\": 1,\"sectionIndex\": 1,\"sectionFactor\": 12,\"controlIndex\": 1},\"addedFromPersistedData\": true,\"innerHTML\":\"Hello World
\"}]' +``` + ## Response The command won't return a response on success. diff --git a/src/m365/spo/commands/page/page-set.spec.ts b/src/m365/spo/commands/page/page-set.spec.ts index 3340ac1c4b7..4c530518a24 100644 --- a/src/m365/spo/commands/page/page-set.spec.ts +++ b/src/m365/spo/commands/page/page-set.spec.ts @@ -484,6 +484,53 @@ describe(commands.PAGE_SET, () => { await command.action(logger, { options: { debug: true, name: 'page.aspx', webUrl: 'https://contoso.sharepoint.com/sites/team-a', title: newPageTitle } }); }); + it('updates page content', async () => { + sinonUtil.restore([request.post]); + + const newContent = [{ + "controlType": 4, + "position": { + "layoutIndex": 1, + "zoneIndex": 1, + "sectionIndex": 1, + "sectionFactor": 12, + "controlIndex": 1 + }, + "addedFromPersistedData": true, + "innerHTML": "Text content
" + }]; + + const initialPage = { + Title: "article", + Id: 1, + TopicHeader: "TopicHeader", + AuthorByline: "AuthorByline", + Description: "Description", + BannerImageUrl: { + Description: '/_layouts/15/images/sitepagethumbnail.png', + Url: `https://contoso.sharepoint.com/_layouts/15/images/sitepagethumbnail.png` + }, + CanvasContent1: "{}", + LayoutWebpartsContent: "{}" + }; + + let data: string = ''; + sinon.stub(request, 'post').callsFake(async (opts) => { + if ((opts.url as string).includes(`/_api/sitepages/pages/GetByUrl('sitepages/page.aspx')/checkoutpage`)) { + return initialPage; + } + if ((opts.url as string).includes(`/_api/SitePages/Pages(1)/SavePage`) || + (opts.url as string).includes(`/_api/SitePages/Pages(1)/SavePageAsDraft`)) { + data = opts.data.CanvasContent1; + return {}; + } + throw 'Invalid request'; + }); + + await command.action(logger, { options: { debug: true, name: 'page.aspx', webUrl: 'https://contoso.sharepoint.com/sites/team-a', content: JSON.stringify(newContent) } }); + assert.deepStrictEqual(JSON.parse(data), newContent); + }); + it('publishes page', async () => { sinonUtil.restore([request.post]); @@ -780,4 +827,14 @@ describe(commands.PAGE_SET, () => { const actual = await command.validate({ options: { name: 'page.aspx', webUrl: 'https://contoso.sharepoint.com', commentsEnabled: false } }, commandInfo); assert.strictEqual(actual, true); }); + + it('fails validation if content is not valid JSON', async () => { + const actual = await command.validate({ options: { name: 'page.aspx', webUrl: 'https://contoso.sharepoint.com', content: "foo" } }, commandInfo); + assert.notStrictEqual(actual, true); + }); + + it('passes validation if content is valid JSON', async () => { + const actual = await command.validate({ options: { name: 'page.aspx', webUrl: 'https://contoso.sharepoint.com', content: "[]" } }, commandInfo); + assert.strictEqual(actual, true); + }); }); \ No newline at end of file diff --git a/src/m365/spo/commands/page/page-set.ts b/src/m365/spo/commands/page/page-set.ts index baa9a4ef625..4af3378b691 100644 --- a/src/m365/spo/commands/page/page-set.ts +++ b/src/m365/spo/commands/page/page-set.ts @@ -29,6 +29,7 @@ interface Options extends GlobalOptions { description?: string; title?: string; demoteFrom?: string; + content?: string; } class SpoPageSetCommand extends SpoCommand { @@ -59,7 +60,8 @@ class SpoPageSetCommand extends SpoCommand { publish: args.options.publish || false, publishMessage: typeof args.options.publishMessage !== 'undefined', description: typeof args.options.description !== 'undefined', - title: typeof args.options.title !== 'undefined' + title: typeof args.options.title !== 'undefined', + content: typeof args.options.content !== 'undefined' }); }); } @@ -99,6 +101,9 @@ class SpoPageSetCommand extends SpoCommand { }, { option: '--title [title]' + }, + { + option: '--content [content]' } ); } @@ -138,6 +143,15 @@ class SpoPageSetCommand extends SpoCommand { return 'You can only promote article pages as news article'; } + if (args.options.content) { + try { + JSON.parse(args.options.content); + } + catch (e) { + return `Specified content is not a valid JSON string. Input: ${args.options.content}. Error: ${e}`; + } + } + return true; } ); @@ -174,7 +188,7 @@ class SpoPageSetCommand extends SpoCommand { pageId = page.Id; bannerImageUrl = page.BannerImageUrl; - canvasContent1 = page.CanvasContent1; + canvasContent1 = args.options.content || page.CanvasContent1; layoutWebpartsContent = page.LayoutWebpartsContent; pageDescription = pageDescription || page.Description; topicHeader = page.TopicHeader;