diff --git a/docs/docs/cmd/spo/page/page-section-remove.mdx b/docs/docs/cmd/spo/page/page-section-remove.mdx new file mode 100644 index 00000000000..919d737a129 --- /dev/null +++ b/docs/docs/cmd/spo/page/page-section-remove.mdx @@ -0,0 +1,47 @@ +import Global from '/docs/cmd/_global.mdx'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# spo page section remove + +Removes the specified section from a modern page + +## Usage + +```sh +m365 spo page section remove [options] +``` + +## Options + +```md definition-remove +`-u, --webUrl ` +: URL of the site where the page is located. + +`-n, --pageName ` +: Name of the page from which to remove a section. + +`-s, --section
` +: ID of the section to be removed. + +`-f, --force` +: Don't prompt for confirmation. +``` + + + +## Remarks + +If the specified `pageName` doesn't refer to an existing modern page, you will get a _File doesn't exists_ error. + +## Examples + +Remove section of a modern page + +```sh +m365 spo page section remove --webUrl https://contoso.sharepoint.com/sites/team-a --pageName home.aspx --section 1 +``` + +## Response + +The command won't return a response on success. diff --git a/docs/src/config/sidebars.ts b/docs/src/config/sidebars.ts index 5f409dc53c9..58925d9c264 100644 --- a/docs/src/config/sidebars.ts +++ b/docs/src/config/sidebars.ts @@ -3384,6 +3384,11 @@ const sidebars: SidebarsConfig = { label: 'page section list', id: 'cmd/spo/page/page-section-list' }, + { + type: 'doc', + label: 'page section remove', + id: 'cmd/spo/page/page-section-remove' + }, { type: 'doc', label: 'page template list', diff --git a/src/m365/spo/commands.ts b/src/m365/spo/commands.ts index a79a86184ce..e415c899558 100644 --- a/src/m365/spo/commands.ts +++ b/src/m365/spo/commands.ts @@ -221,6 +221,7 @@ export default { PAGE_SECTION_ADD: `${prefix} page section add`, PAGE_SECTION_GET: `${prefix} page section get`, PAGE_SECTION_LIST: `${prefix} page section list`, + PAGE_SECTION_REMOVE: `${prefix} page section remove`, PAGE_TEMPLATE_LIST: `${prefix} page template list`, PAGE_TEMPLATE_REMOVE: `${prefix} page template remove`, PAGE_TEXT_ADD: `${prefix} page text add`, diff --git a/src/m365/spo/commands/page/page-section-remove.spec.ts b/src/m365/spo/commands/page/page-section-remove.spec.ts new file mode 100644 index 00000000000..50b9ba75ae4 --- /dev/null +++ b/src/m365/spo/commands/page/page-section-remove.spec.ts @@ -0,0 +1,500 @@ +import assert from 'assert'; +import sinon from 'sinon'; +import auth from '../../../../Auth.js'; +import { cli } from '../../../../cli/cli.js'; +import { CommandInfo } from '../../../../cli/CommandInfo.js'; +import { Logger } from '../../../../cli/Logger.js'; +import { CommandError } from '../../../../Command.js'; +import request from '../../../../request.js'; +import { telemetry } from '../../../../telemetry.js'; +import { pid } from '../../../../utils/pid.js'; +import { session } from '../../../../utils/session.js'; +import { sinonUtil } from '../../../../utils/sinonUtil.js'; +import commands from '../../commands.js'; +import command from './page-section-remove.js'; +import { spo } from '../../../../utils/spo.js'; + +describe(commands.PAGE_SECTION_REMOVE, () => { + let log: string[]; + let logger: Logger; + let commandInfo: CommandInfo; + + const apiResponse = { + "ListItemAllFields": { + "FileSystemObjectType": 0, + "Id": 9, + "ServerRedirectedEmbedUri": null, + "ServerRedirectedEmbedUrl": "", + "ContentTypeId": "0x0101009D1CB255DA76424F860D91F20E6C41180070E97A63FCC58F47B8FE04D0654FD44E", + "WikiField": null, + "Title": "Nova", + "ClientSideApplicationId": "b6917cb1-93a0-4b97-a84d-7cf49975d4ec", + "CanvasContent1": "

asd

\n
", + "BannerImageUrl": { + "Description": "/_layouts/15/images/sitepagethumbnail.png", + "Url": "/_layouts/15/images/sitepagethumbnail.png" + }, + "Description": "asd", + "PromotedState": 0, + "FirstPublishedDate": null, + "LayoutWebpartsContent": "
", + "ComplianceAssetId": null, + "OData__AuthorBylineId": null, + "_AuthorBylineStringId": null, + "OData__OriginalSourceUrl": null, + "OData__OriginalSourceSiteId": null, + "OData__OriginalSourceWebId": null, + "OData__OriginalSourceListId": null, + "OData__OriginalSourceItemId": null, + "ID": 9, + "Created": "2018-07-11T16:24:12", + "AuthorId": 9, + "Modified": "2018-07-11T16:33:57", + "EditorId": 9, + "OData__CopySource": null, + "CheckoutUserId": 9, + "OData__UIVersionString": "1.0", + "GUID": "903cdabe-7a28-4e96-a55e-c768185d7d9a" + }, + "CheckInComment": "", + "CheckOutType": 0, + "ContentTag": "{16035D61-EDB9-4758-A490-3D13FCD9FDAA},10,8", + "CustomizedPageStatus": 0, + "ETag": "\"{16035D61-EDB9-4758-A490-3D13FCD9FDAA},10\"", + "Exists": true, + "IrmEnabled": false, + "Length": "4708", + "Level": 255, + "LinkingUri": null, + "LinkingUrl": "", + "MajorVersion": 1, + "MinorVersion": 0, + "Name": "Nova.aspx", + "ServerRelativeUrl": "/SitePages/Nova.aspx", + "TimeCreated": "2018-07-11T19:24:12Z", + "TimeLastModified": "2018-07-11T19:33:57Z", + "Title": "Nova", + "UIVersion": 512, + "UIVersionLabel": "1.0", + "UniqueId": "16035d61-edb9-4758-a490-3d13fcd9fdaa" + }; + + before(() => { + sinon.stub(auth, 'restoreAuth').resolves(); + sinon.stub(telemetry, 'trackEvent').returns(); + sinon.stub(pid, 'getProcessName').returns(''); + sinon.stub(session, 'getId').returns(''); + sinon.stub(spo, 'getRequestDigest').resolves({ + FormDigestValue: 'ABC', + FormDigestTimeoutSeconds: 1800, + FormDigestExpiresAt: new Date(), + WebFullUrl: 'https://contoso.sharepoint.com' + }); + auth.connection.active = true; + commandInfo = cli.getCommandInfo(command); + }); + + beforeEach(() => { + log = []; + logger = { + log: async (msg: string) => { + log.push(msg); + }, + logRaw: async (msg: string) => { + log.push(msg); + }, + logToStderr: async (msg: string) => { + log.push(msg); + } + }; + }); + + afterEach(() => { + sinonUtil.restore([ + request.get, + request.post, + cli.promptForConfirmation + ]); + }); + + after(() => { + sinon.restore(); + auth.connection.active = false; + }); + + it('has correct name', () => { + assert.strictEqual(command.name, commands.PAGE_SECTION_REMOVE); + }); + + it('has a description', () => { + assert.notStrictEqual(command.description, null); + }); + + it('aborts removing section when prompt not confirmed', async () => { + const confirmationStub = sinon.stub(cli, 'promptForConfirmation').resolves(false); + await command.action(logger, { options: { webUrl: 'https://contoso.sharepoint.com/sites/team-a', pageName: 'home.aspx', section: 1 } }); + + assert(confirmationStub.calledOnce); + }); + + it('removes section when prompt is confirmed', async () => { + sinon.stub(cli, 'promptForConfirmation').resolves(true); + + sinon.stub(request, 'get').callsFake(async (opts) => { + if ((opts.url as string).indexOf(`/_api/web/GetFileByServerRelativePath(DecodedUrl='/sites/team-a/SitePages/home.aspx')`) > -1) { + return apiResponse; + } + + throw 'Invalid request'; + }); + + + const postStub = sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/sites/team-a/_api/web/GetFileByServerRelativePath(DecodedUrl='/sites/team-a/sitepages/home.aspx')/ListItemAllFields`) { + return; + } + + throw `Invalid request ${opts.url}`; + }); + + await command.action(logger, { options: { webUrl: 'https://contoso.sharepoint.com/sites/team-a', pageName: 'home.aspx', section: 1 } }); + + assert(postStub.calledOnce); + }); + + it('removes section from the modern page', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if ((opts.url as string).indexOf(`/_api/web/GetFileByServerRelativePath(DecodedUrl='/sites/team-a/SitePages/home.aspx')`) > -1) { + return apiResponse; + } + + throw 'Invalid request'; + }); + + const postStub = sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/sites/team-a/_api/web/GetFileByServerRelativePath(DecodedUrl='/sites/team-a/sitepages/home.aspx')/ListItemAllFields`) { + return; + } + + throw `Invalid request ${opts.url}`; + }); + + await command.action(logger, { options: { webUrl: 'https://contoso.sharepoint.com/sites/team-a', pageName: 'home.aspx', section: 1, force: true } }); + assert.deepStrictEqual(postStub.lastCall.args[0].data, { + CanvasContent1: "
" + }); + }); + + it('removes a section from the modern page while preserving other collapsible sections', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if ((opts.url as string).indexOf(`/_api/web/GetFileByServerRelativePath(DecodedUrl='/sites/team-a/SitePages/home.aspx')`) > -1) { + return { + "ListItemAllFields": { + "FileSystemObjectType": 0, + "Id": 9, + "ServerRedirectedEmbedUri": null, + "ServerRedirectedEmbedUrl": "", + "ContentTypeId": "0x0101009D1CB255DA76424F860D91F20E6C41180070E97A63FCC58F47B8FE04D0654FD44E", + "WikiField": null, + "Title": "Nova", + "ClientSideApplicationId": "b6917cb1-93a0-4b97-a84d-7cf49975d4ec", + "CanvasContent1": "

test

Test

", + "BannerImageUrl": { + "Description": "/_layouts/15/images/sitepagethumbnail.png", + "Url": "/_layouts/15/images/sitepagethumbnail.png" + }, + "Description": "asd", + "PromotedState": 0, + "FirstPublishedDate": null, + "LayoutWebpartsContent": "", + "ComplianceAssetId": null, + "OData__AuthorBylineId": null, + "_AuthorBylineStringId": null, + "OData__OriginalSourceUrl": null, + "OData__OriginalSourceSiteId": null, + "OData__OriginalSourceWebId": null, + "OData__OriginalSourceListId": null, + "OData__OriginalSourceItemId": null, + "ID": 9, + "Created": "2018-07-11T16:24:12", + "AuthorId": 9, + "Modified": "2018-07-11T16:33:57", + "EditorId": 9, + "OData__CopySource": null, + "CheckoutUserId": 9, + "OData__UIVersionString": "1.0", + "GUID": "903cdabe-7a28-4e96-a55e-c768185d7d9a" + }, + "CheckInComment": "", + "CheckOutType": 0, + "ContentTag": "{16035D61-EDB9-4758-A490-3D13FCD9FDAA},10,8", + "CustomizedPageStatus": 0, + "ETag": "\"{16035D61-EDB9-4758-A490-3D13FCD9FDAA},10\"", + "Exists": true, + "IrmEnabled": false, + "Length": "4708", + "Level": 255, + "LinkingUri": null, + "LinkingUrl": "", + "MajorVersion": 1, + "MinorVersion": 0, + "Name": "Nova.aspx", + "ServerRelativeUrl": "/SitePages/Nova.aspx", + "TimeCreated": "2018-07-11T19:24:12Z", + "TimeLastModified": "2018-07-11T19:33:57Z", + "Title": "Nova", + "UIVersion": 512, + "UIVersionLabel": "1.0", + "UniqueId": "16035d61-edb9-4758-a490-3d13fcd9fdaa" + }; + } + + throw 'Invalid request'; + }); + + const postStub = sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/sites/team-a/_api/web/GetFileByServerRelativePath(DecodedUrl='/sites/team-a/sitepages/home.aspx')/ListItemAllFields`) { + return; + } + + throw `Invalid request ${opts.url}`; + }); + + await command.action(logger, { options: { webUrl: 'https://contoso.sharepoint.com/sites/team-a', pageName: 'home.aspx', section: 1, force: true } }); + assert.deepStrictEqual(postStub.lastCall.args[0].data, { + CanvasContent1: '

Test

' + }); + }); + + it('removes a section from the modern page while preserving other sections with background', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if ((opts.url as string).indexOf(`/_api/web/GetFileByServerRelativePath(DecodedUrl='/sites/team-a/SitePages/home.aspx')`) > -1) { + return { + "ListItemAllFields": { + "FileSystemObjectType": 0, + "Id": 9, + "ServerRedirectedEmbedUri": null, + "ServerRedirectedEmbedUrl": "", + "ContentTypeId": "0x0101009D1CB255DA76424F860D91F20E6C41180070E97A63FCC58F47B8FE04D0654FD44E", + "WikiField": null, + "Title": "Nova", + "ClientSideApplicationId": "b6917cb1-93a0-4b97-a84d-7cf49975d4ec", + "CanvasContent1": "

test

Test

", + "BannerImageUrl": { + "Description": "/_layouts/15/images/sitepagethumbnail.png", + "Url": "/_layouts/15/images/sitepagethumbnail.png" + }, + "Description": "asd", + "PromotedState": 0, + "FirstPublishedDate": null, + "LayoutWebpartsContent": "", + "ComplianceAssetId": null, + "OData__AuthorBylineId": null, + "_AuthorBylineStringId": null, + "OData__OriginalSourceUrl": null, + "OData__OriginalSourceSiteId": null, + "OData__OriginalSourceWebId": null, + "OData__OriginalSourceListId": null, + "OData__OriginalSourceItemId": null, + "ID": 9, + "Created": "2018-07-11T16:24:12", + "AuthorId": 9, + "Modified": "2018-07-11T16:33:57", + "EditorId": 9, + "OData__CopySource": null, + "CheckoutUserId": 9, + "OData__UIVersionString": "1.0", + "GUID": "903cdabe-7a28-4e96-a55e-c768185d7d9a" + }, + "CheckInComment": "", + "CheckOutType": 0, + "ContentTag": "{16035D61-EDB9-4758-A490-3D13FCD9FDAA},10,8", + "CustomizedPageStatus": 0, + "ETag": "\"{16035D61-EDB9-4758-A490-3D13FCD9FDAA},10\"", + "Exists": true, + "IrmEnabled": false, + "Length": "4708", + "Level": 255, + "LinkingUri": null, + "LinkingUrl": "", + "MajorVersion": 1, + "MinorVersion": 0, + "Name": "Nova.aspx", + "ServerRelativeUrl": "/SitePages/Nova.aspx", + "TimeCreated": "2018-07-11T19:24:12Z", + "TimeLastModified": "2018-07-11T19:33:57Z", + "Title": "Nova", + "UIVersion": 512, + "UIVersionLabel": "1.0", + "UniqueId": "16035d61-edb9-4758-a490-3d13fcd9fdaa" + }; + } + + throw 'Invalid request'; + }); + + const postStub = sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/sites/team-a/_api/web/GetFileByServerRelativePath(DecodedUrl='/sites/team-a/sitepages/home.aspx')/ListItemAllFields`) { + return; + } + + throw `Invalid request ${opts.url}`; + }); + + await command.action(logger, { options: { webUrl: 'https://contoso.sharepoint.com/sites/team-a', pageName: 'home.aspx', section: 1, force: true } }); + assert.deepStrictEqual(postStub.lastCall.args[0].data, { + CanvasContent1: '

Test

' + }); + }); + + it('removes sections on the modern page (debug)', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if ((opts.url as string).indexOf(`/_api/web/GetFileByServerRelativePath(DecodedUrl='/sites/team-a/SitePages/home.aspx')`) > -1) { + return apiResponse; + } + + throw 'Invalid request'; + }); + + const postStub = sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === `https://contoso.sharepoint.com/sites/team-a/_api/web/GetFileByServerRelativePath(DecodedUrl='/sites/team-a/sitepages/home.aspx')/ListItemAllFields`) { + return; + } + + throw `Invalid request ${opts.url}`; + }); + + await command.action(logger, { options: { debug: true, webUrl: 'https://contoso.sharepoint.com/sites/team-a', pageName: 'home.aspx', section: 1, force: true } }); + assert.deepStrictEqual(postStub.lastCall.args[0].data, { + CanvasContent1: '
' + }); + }); + + it('shows error when the specified page is a classic page', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if ((opts.url as string).indexOf(`/_api/web/GetFileByServerRelativePath(DecodedUrl='/sites/team-a/SitePages/home.aspx')`) > -1) { + return { + "ListItemAllFields": { + "CommentsDisabled": false, + "FileSystemObjectType": 0, + "Id": 1, + "ServerRedirectedEmbedUri": null, + "ServerRedirectedEmbedUrl": "", + "ContentTypeId": "0x0101080088E2A2ED69D0324A8981DD7FAC103494", + "FileLeafRef": "Home.aspx", + "ComplianceAssetId": null, + "WikiField": "
\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\r\n true,false,2
", + "Title": null, + "ClientSideApplicationId": null, + "PageLayoutType": null, + "CanvasContent1": null, + "BannerImageUrl": null, + "Description": null, + "PromotedState": null, + "FirstPublishedDate": null, + "LayoutWebpartsContent": null, + "AuthorsId": null, + "AuthorsStringId": null, + "OriginalSourceUrl": null, + "ID": 1, + "Created": "2018-03-19T17:52:56", + "AuthorId": 1073741823, + "Modified": "2018-03-24T07:14:28", + "EditorId": 1073741823, + "OData__CopySource": null, + "CheckoutUserId": null, + "OData__UIVersionString": "1.0", + "GUID": "19ac5510-bba6-427b-9c1b-a3329a3b0cad" + }, + "CheckInComment": "", + "CheckOutType": 2, + "ContentTag": "{8F33F78C-9F39-48E2-B99D-01C2937A56BB},4,1", + "CustomizedPageStatus": 1, + "ETag": "\"{8F33F78C-9F39-48E2-B99D-01C2937A56BB},4\"", + "Exists": true, + "IrmEnabled": false, + "Length": "3356", + "Level": 1, + "LinkingUri": null, + "LinkingUrl": "", + "MajorVersion": 1, + "MinorVersion": 0, + "Name": "home.aspx", + "ServerRelativeUrl": "/sites/team-a/SitePages/home.aspx", + "TimeCreated": "2018-03-20T00:52:56Z", + "TimeLastModified": "2018-03-24T14:14:28Z", + "Title": null, + "UIVersion": 512, + "UIVersionLabel": "1.0", + "UniqueId": "8f33f78c-9f39-48e2-b99d-01c2937a56bb" + }; + } + + throw 'Invalid request'; + }); + + await assert.rejects(command.action(logger, { options: { webUrl: 'https://contoso.sharepoint.com/sites/team-a', pageName: 'home.aspx', section: 1, force: true } } as any), + new CommandError('Page home.aspx is not a modern page.')); + }); + + it('correctly handles page not found', async () => { + sinon.stub(request, 'get').callsFake(() => { + throw { + error: { + "odata.error": { + "code": "-2130575338, Microsoft.SharePoint.SPException", + "message": { + "lang": "en-US", + "value": "The file /sites/team-a/SitePages/home1.aspx does not exist." + } + } + } + }; + }); + + await assert.rejects(command.action(logger, { options: { webUrl: 'https://contoso.sharepoint.com/sites/team-a', pageName: 'home.aspx', section: 1, force: true } } as any), + new CommandError('The file /sites/team-a/SitePages/home1.aspx does not exist.')); + }); + + it('correctly handles OData error when retrieving pages', async () => { + sinon.stub(request, 'get').callsFake(() => { + throw { error: { 'odata.error': { message: { value: 'An error has occurred' } } } }; + }); + + await assert.rejects(command.action(logger, { options: { webUrl: 'https://contoso.sharepoint.com/sites/team-a', pageName: 'home.aspx', section: 1, force: true } } as any), + new CommandError('An error has occurred')); + }); + + it('correctly handles section not found error', async () => { + sinon.stub(request, 'get').callsFake(async (opts) => { + if ((opts.url as string).indexOf(`/_api/web/GetFileByServerRelativePath(DecodedUrl='/sites/team-a/SitePages/home.aspx')`) > -1) { + return apiResponse; + } + + throw 'Invalid request'; + }); + + await assert.rejects(command.action(logger, { options: { webUrl: 'https://contoso.sharepoint.com/sites/team-a', pageName: 'home.aspx', section: -1, force: true } } as any), + new CommandError('Section -1 not found')); + }); + + it('fails validation if the webUrl option is not a valid SharePoint site URL', async () => { + const actual = await command.validate({ options: { webUrl: 'foo', pageName: 'home.aspx', section: 1 } }, commandInfo); + assert.notStrictEqual(actual, true); + }); + + it('fails validation if the section option is not a number', async () => { + const actual = await command.validate({ options: { webUrl: 'https://contoso.sharepoint.com', pageName: 'home.aspx', section: 'abc' } }, commandInfo); + assert.notStrictEqual(actual, true); + }); + + it('passes validation when the webUrl is a valid SharePoint URL and name is specified and section is specified', async () => { + const actual = await command.validate({ options: { webUrl: 'https://contoso.sharepoint.com', pageName: 'home.aspx', section: 1 } }, commandInfo); + assert.strictEqual(actual, true); + }); + + it('passes validation when the webUrl is a valid SharePoint URL and name, section and force are specified', async () => { + const actual = await command.validate({ options: { webUrl: 'https://contoso.sharepoint.com', pageName: 'home.aspx', section: 1, force: false } }, commandInfo); + assert.strictEqual(actual, true); + }); +}); diff --git a/src/m365/spo/commands/page/page-section-remove.ts b/src/m365/spo/commands/page/page-section-remove.ts new file mode 100644 index 00000000000..8e30a886631 --- /dev/null +++ b/src/m365/spo/commands/page/page-section-remove.ts @@ -0,0 +1,136 @@ +import { cli } from '../../../../cli/cli.js'; +import { Logger } from '../../../../cli/Logger.js'; +import GlobalOptions from '../../../../GlobalOptions.js'; +import request from '../../../../request.js'; +import { spo } from '../../../../utils/spo.js'; +import { urlUtil } from '../../../../utils/urlUtil.js'; +import { validation } from '../../../../utils/validation.js'; +import SpoCommand from '../../../base/SpoCommand.js'; +import commands from '../../commands.js'; +import { Page } from './Page.js'; + +interface CommandArgs { + options: Options; +} + +interface Options extends GlobalOptions { + pageName: string; + section: number; + webUrl: string; + force: string; +} + +class SpoPageSectionRemoveCommand extends SpoCommand { + public get name(): string { + return commands.PAGE_SECTION_REMOVE; + } + + public get description(): string { + return 'Remove the specified section from the modern page'; + } + + constructor() { + super(); + + this.#initOptions(); + this.#initValidators(); + this.#initTelemetry(); + } + + #initTelemetry(): void { + this.telemetry.push((args: CommandArgs) => { + Object.assign(this.telemetryProperties, { + pageName: typeof args.options.pageName !== 'undefined', + section: typeof args.options.section !== 'undefined', + webUrl: typeof args.options.webUrl !== 'undefined', + force: !!args.options.force + }); + }); + } + + #initOptions(): void { + this.options.unshift( + { + option: '-u, --webUrl ' + }, + { + option: '-n, --pageName ' + }, + { + option: '-s, --section
' + }, + { + option: '-f, --force' + } + ); + } + + #initValidators(): void { + this.validators.push( + async (args: CommandArgs) => { + if (isNaN(args.options.section)) { + return `${args.options.section} is not a number`; + } + + return validation.isValidSharePointUrl(args.options.webUrl); + } + ); + } + + public async commandAction(logger: Logger, args: CommandArgs): Promise { + if (args.options.force) { + await this.removeSection(logger, args); + } + else { + const result = await cli.promptForConfirmation({ message: `Are you sure you want to remove section ${args.options.section} from '${args.options.pageName}'?` }); + + if (result) { + await this.removeSection(logger, args); + } + } + } + + private async removeSection(logger: Logger, args: CommandArgs): Promise { + try { + if (this.verbose) { + await logger.logToStderr(`Removing modern page section ${args.options.pageName} - ${args.options.section}...`); + } + const reqDigest = await spo.getRequestDigest(args.options.webUrl); + const clientSidePage = await Page.getPage(args.options.pageName, args.options.webUrl, logger, this.debug, this.verbose); + + const sectionToDelete = clientSidePage.sections + .findIndex(section => section.order === args.options.section); + + if (sectionToDelete === -1) { + throw new Error(`Section ${args.options.section} not found`); + } + + clientSidePage.sections.splice(sectionToDelete, 1); + + const updatedContent = clientSidePage.toHtml(); + + const requestOptions: any = { + url: `${args.options + .webUrl}/_api/web/GetFileByServerRelativePath(DecodedUrl='${urlUtil.getServerRelativeSiteUrl(args.options.webUrl)}/sitepages/${args.options.pageName}')/ListItemAllFields`, + headers: { + 'X-RequestDigest': reqDigest.FormDigestValue, + 'content-type': 'application/json;odata=nometadata', + 'X-HTTP-Method': 'MERGE', + 'IF-MATCH': '*', + accept: 'application/json;odata=nometadata' + }, + data: { + CanvasContent1: updatedContent + }, + responseType: 'json' + }; + + return request.post(requestOptions); + } + catch (err: any) { + this.handleRejectedODataJsonPromise(err); + } + } +} + +export default new SpoPageSectionRemoveCommand(); \ No newline at end of file