diff --git a/package.json b/package.json index e5f5aa6..f0fc5c9 100644 --- a/package.json +++ b/package.json @@ -466,6 +466,11 @@ "category": "SharePoint Framework Toolkit", "icon": "$(sync)" }, + { + "command": "spfx-toolkit.setFormCustomizer", + "title": "Set Form Customizer", + "category": "SharePoint Framework Toolkit" + }, { "command": "spfx-toolkit.showMoreActions", "title": "...", diff --git a/src/constants/Commands.ts b/src/constants/Commands.ts index f26f472..9b5869d 100644 --- a/src/constants/Commands.ts +++ b/src/constants/Commands.ts @@ -60,5 +60,8 @@ export const Commands = { enableAppCatalogApp: `${EXTENSION_NAME}.enableAppCatalogApp`, disableAppCatalogApp: `${EXTENSION_NAME}.disableAppCatalogApp`, upgradeAppCatalogApp: `${EXTENSION_NAME}.upgradeAppCatalogApp`, - showMoreActions: `${EXTENSION_NAME}.showMoreActions` + showMoreActions: `${EXTENSION_NAME}.showMoreActions`, + + // Set form customizer + setFormCustomizer: `${EXTENSION_NAME}.setFormCustomizer` }; \ No newline at end of file diff --git a/src/panels/CommandPanel.ts b/src/panels/CommandPanel.ts index a874882..b2635c6 100644 --- a/src/panels/CommandPanel.ts +++ b/src/panels/CommandPanel.ts @@ -310,6 +310,7 @@ export class CommandPanel { actionCommands.push(new ActionTreeItem('Add new component', '', { name: 'add', custom: false }, undefined, Commands.addToProject)); actionCommands.push(new ActionTreeItem('Scaffold CI/CD Workflow', '', { name: 'rocket', custom: false }, undefined, Commands.pipeline)); + actionCommands.push(new ActionTreeItem('Set Form Customizer', '', { name: 'checklist', custom: false }, undefined, Commands.setFormCustomizer)); actionCommands.push(new ActionTreeItem('View samples', '', { name: 'library', custom: false }, undefined, Commands.samplesGallery)); window.registerTreeDataProvider('pnp-view-actions', new ActionTreeDataProvider(actionCommands)); diff --git a/src/services/actions/CliActions.ts b/src/services/actions/CliActions.ts index 9d7c997..6922d9e 100644 --- a/src/services/actions/CliActions.ts +++ b/src/services/actions/CliActions.ts @@ -69,6 +69,9 @@ export class CliActions { subscriptions.push( commands.registerCommand(Commands.upgradeAppCatalogApp, CliActions.upgradeAppCatalogApp) ); + subscriptions.push( + commands.registerCommand(Commands.setFormCustomizer, CliActions.setFormCustomizer) + ); } /** @@ -920,4 +923,111 @@ export class CliActions { Notifications.error(`${fileName}.tour file not found in path ${path.join(wsFolder.uri.fsPath, '.tours')}. Cannot start Code Tour.`); } } + + /** + * Sets the form customizer for a content type on a list. + */ + public static async setFormCustomizer() { + const relativeUrl = await window.showInputBox({ + prompt: 'Enter the relative URL of the site', + ignoreFocusOut: true, + placeHolder: 'e.g., sites/sales', + validateInput: (input) => { + if (!input) { + return 'site URL is required'; + } + + const trimmedInput = input.trim(); + + if (trimmedInput.startsWith('https://')) { + return 'Please provide a relative URL, not an absolute URL.'; + } + if (trimmedInput.startsWith('/')) { + return 'Please provide a relative URL without a leading slash.'; + } + + return undefined; + } + }); + + if (relativeUrl === undefined) { + Notifications.warning('No site URL provided. Setting form customizer aborted.'); + return; + } + + const siteUrl = `${EnvironmentInformation.tenantUrl}/${relativeUrl.trim()}`; + + const listTitle = await window.showInputBox({ + prompt: 'Enter the list title', + ignoreFocusOut: true, + validateInput: (value) => value ? undefined : 'List title is required' + }); + + if (!listTitle) { + Notifications.warning('No list title provided. Setting form customizer aborted.'); + return; + } + + const contentType = await window.showInputBox({ + prompt: 'Enter the Content Type name', + ignoreFocusOut: true, + validateInput: (value) => value ? undefined : 'Content Type name is required' + }); + + if (!contentType) { + Notifications.warning('No content type name provided. Setting form customizer aborted.'); + return; + } + + const editFormClientSideComponentId = await window.showInputBox({ + prompt: 'Enter the Edit form customizer ID (leave empty to skip)', + ignoreFocusOut: true + }); + + const newFormClientSideComponentId = await window.showInputBox({ + prompt: 'Enter the New form customizer ID (leave empty to skip)', + ignoreFocusOut: true + }); + + const displayFormClientSideComponentId = await window.showInputBox({ + prompt: 'Enter the View form customizer ID (leave empty to skip)', + ignoreFocusOut: true + }); + + const commandOptions: any = { + webUrl: siteUrl, + listTitle: listTitle, + name: contentType + }; + + if (editFormClientSideComponentId) { + commandOptions.EditFormClientSideComponentId = editFormClientSideComponentId; + } + + if (newFormClientSideComponentId ) { + commandOptions.NewFormClientSideComponentId = newFormClientSideComponentId ; + } + + if (displayFormClientSideComponentId) { + commandOptions.DisplayFormClientSideComponentId = displayFormClientSideComponentId; + } + + await window.withProgress({ + location: ProgressLocation.Notification, + title: 'Setting form customizer...', + cancellable: true + }, async (progress: Progress<{ message?: string; increment?: number }>) => { + try { + const result = await CliExecuter.execute('spo contenttype set', 'json', commandOptions); + if (result.stderr) { + Notifications.error(result.stderr); + } else { + Notifications.info('Form customizer set successfully.'); + } + } catch (e: any) { + const message = e?.error?.message; + Notifications.error(message); + } + }); + } } \ No newline at end of file