您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Automatically creates TypeScript type definitions compatible with @types/xrm by extracting form attributes and controls from Dynamics 365/Power Platform model-driven applications.
当前为
// ==UserScript== // @name Microsoft Power Platform/Dynamics 365 CE - Generate TypeScript Overload Signatures // @namespace https://github.com/gncnpk/xrm-generate-ts-overloads // @author Gavin Canon-Phratsachack (https://github.com/gncnpk) // @version 1.93 // @license GPL-3.0 // @description Automatically creates TypeScript type definitions compatible with @types/xrm by extracting form attributes and controls from Dynamics 365/Power Platform model-driven applications. // @match https://*.dynamics.com/main.aspx?appid=*&pagetype=entityrecord&etn=*&id=* // @grant none // ==/UserScript== (function() { 'use strict'; const generateComments = function(fieldName, fieldValue) { return `\n/** ${fieldName}: ${fieldValue} */` } // Create a button element and style it to be fixed in the bottom-right corner. const btn = document.createElement('button'); btn.textContent = 'Generate TypeScript Signatures'; btn.style.position = 'fixed'; btn.style.bottom = '20px'; btn.style.right = '20px'; btn.style.padding = '10px'; btn.style.backgroundColor = '#007ACC'; btn.style.color = '#fff'; btn.style.border = 'none'; btn.style.borderRadius = '5px'; btn.style.cursor = 'pointer'; btn.style.zIndex = 10000; document.body.appendChild(btn); btn.addEventListener('click', () => { // Mapping objects for Xrm attribute and control types. var attributeTypeMapping = { "boolean": "Xrm.Attributes.BooleanAttribute", "datetime": "Xrm.Attributes.DateAttribute", "decimal": "Xrm.Attributes.NumberAttribute", "double": "Xrm.Attributes.NumberAttribute", "integer": "Xrm.Attributes.NumberAttribute", "lookup": "Xrm.Attributes.LookupAttribute", "memo": "Xrm.Attributes.StringAttribute", "money": "Xrm.Attributes.NumberAttribute", "multiselectoptionset": "Xrm.Attributes.MultiselectOptionSetAttribute", "optionset": "Xrm.Attributes.OptionSetAttribute", "string": "Xrm.Attributes.StringAttribute" }; var controlTypeMapping = { "standard": "Xrm.Controls.StandardControl", "iframe": "Xrm.Controls.IframeControl", "lookup": "Xrm.Controls.LookupControl", "optionset": "Xrm.Controls.OptionSetControl", "customsubgrid:MscrmControls.Grid.GridControl": "Xrm.Controls.GridControl", "subgrid": "Xrm.Controls.GridControl", "timelinewall": "Xrm.Controls.TimelineWall", "quickform": "Xrm.Controls.QuickFormControl" }; var specificControlTypeMapping = { "boolean": "Xrm.Controls.BooleanControl", "datetime": "Xrm.Controls.DateControl", "decimal": "Xrm.Controls.NumberControl", "double": "Xrm.Controls.NumberControl", "integer": "Xrm.Controls.NumberControl", "lookup": "Xrm.Controls.LookupControl", "memo": "Xrm.Controls.StringControl", "money": "Xrm.Controls.NumberControl", "multiselectoptionset": "Xrm.Controls.MultiselectOptionSetControl", "optionset": "Xrm.Controls.OptionSetControl", "string": "Xrm.Controls.StringControl" } // Object to hold the type information. const typeInfo = { attributes: {}, controls: {}, possibleTypes: {}, enums: {} } // Loop through all controls on the form. if (typeof Xrm !== 'undefined' && Xrm.Page && typeof Xrm.Page.getControl === 'function') { Xrm.Page.getControl().forEach((ctrl) => { const ctrlType = ctrl.getControlType(); const mappedType = controlTypeMapping[ctrlType]; if (mappedType) { typeInfo.controls[ctrl.getName()] = mappedType; typeInfo.possibleTypes[ctrl.getName()] = []; typeInfo.possibleTypes[ctrl.getName()].push(mappedType); } }); } else { alert("Xrm.Page is not available on this page."); return; } // Loop through all Quick View controls on the form. if (typeof Xrm.Page.ui.quickForms.get === 'function') { Xrm.Page.ui.quickForms.get().forEach((ctrl) => { const ctrlType = ctrl.getControlType(); const mappedType = controlTypeMapping[ctrlType]; if (mappedType) { typeInfo.possibleTypes[ctrl.getName()] = typeInfo.possibleTypes[ctrl.getName()] ?? []; typeInfo.possibleTypes[ctrl.getName()].push(mappedType); } }); } // Loop through all tabs and sections on the form. if (typeof Xrm.Page.ui.tabs.get === 'function') { Xrm.Page.ui.tabs.get().forEach((tab) => { typeInfo.possibleTypes[tab.getName()] = typeInfo.possibleTypes[tab.getName()] ?? []; typeInfo.possibleTypes[tab.getName()].push("Xrm.Controls.Tab"); tab.sections.forEach((section) => { typeInfo.possibleTypes[section.getName()] = typeInfo.possibleTypes[section.getName()] ?? []; typeInfo.possibleTypes[section.getName()].push("Xrm.Controls.Section"); }); }); } // Loop through all attributes on the form. if (typeof Xrm.Page.getAttribute === 'function') { Xrm.Page.getAttribute().forEach((attr) => { const attrType = attr.getAttributeType(); const attrName = attr.getName(); const mappedType = attributeTypeMapping[attrType]; const mappedControlType = specificControlTypeMapping[attrType]; if (mappedType) { typeInfo.attributes[attrName] = mappedType; typeInfo.controls[attrName] = mappedControlType; typeInfo.possibleTypes[attrName] = []; if (attrType !== "optionset") { typeInfo.possibleTypes[attrName].push(mappedType); typeInfo.possibleTypes[attrName].push(mappedControlType); } } if (attr.getAttributeType() === "optionset" && attr.controls.get().length > 0) { const enumValues = attr.getOptions(); if (enumValues) { typeInfo.enums[attrName] = {attribute: "", values: []}; typeInfo.enums[attrName].values = enumValues; typeInfo.enums[attrName].attribute = attrName; typeInfo.attributes[attrName] = `${attrName}_attribute`; typeInfo.possibleTypes[attrName].push(`${attrName}_attribute`); typeInfo.possibleTypes[attrName].push(mappedControlType); } } }); } // Loop through all subgrids on the form. //if (typeof Xrm.Page.getControl === 'function') { // Xrm.Page.getControl().forEach((ctrl) => { // if (ctrl.getControlType() === "subgrid" || ctrl.getControlType() === "customsubgrid:MscrmControls.Grid.GridControl") { // const gridRow = ctrl.getGrid().getRows().get(0); // if (gridRow !== null) { // gridRow.data.entity.attributes.forEach((attr) => { // const attrType = attr.getAttributeType(); // const attrName = attr.getName(); // const mappedType = attributeTypeMapping[attrType]; // const mappedControlType = specificControlTypeMapping[attrType]; // if (mappedType) { // typeInfo.attributes[attrName] = mappedType; // typeInfo.controls[attrName] = mappedControlType; // typeInfo.possibleTypes[attrName] = []; // if (attrType !== "optionset") { // typeInfo.possibleTypes[attrName].push(mappedType); // typeInfo.possibleTypes[attrName].push(mappedControlType); // } // } // if (attr.getAttributeType() === "optionset" && attr.controls.get().length > 0) { // const enumValues = attr.getOptions(); // if (enumValues) { // typeInfo.enums[attrName] = {attribute: "", values: []}; // typeInfo.enums[attrName].values = enumValues; // typeInfo.enums[attrName].attribute = attrName; // typeInfo.attributes[attrName] = `${attrName}_attribute`; // typeInfo.possibleTypes[attrName].push(`${attrName}_attribute`); // typeInfo.possibleTypes[attrName].push(mappedControlType); // } // } // }); // } // }}); // } // // Build the TypeScript overload string. let outputTS = `// This file is generated automatically. // It extends the Xrm.FormContext interface with overloads for getAttribute and getControl. // Do not modify this file manually. ` for(const [originalEnumName, enumValues] of Object.entries(typeInfo.enums)) { let enumName = `${originalEnumName}_enum`; let enumTemplate = []; let textLiteralTypes = []; let valueLiteralTypes = []; for(const enumValue of enumValues.values) { enumTemplate.push(` ${enumValue.text.replace(/\W/g, '').replace(/[0-9]/g, '')} = ${enumValue.value}`); textLiteralTypes.push(`"${enumValue.text}"`); valueLiteralTypes.push(`${enumName}.${enumValue.text.replace(/\W/g, '').replace(/[0-9]/g, '')}`); } outputTS += ` const enum ${enumName} { ${enumTemplate.join(",\n")} } ` outputTS += ` interface ${enumValues.attribute}_value extends Xrm.OptionSetValue { text: ${textLiteralTypes.join(" | ")}; value: ${valueLiteralTypes.join(" | ")}; } ` outputTS += ` interface ${enumValues.attribute}_attribute extends Xrm.Attributes.OptionSetAttribute { getOptions(): ${enumValues.attribute}_value[]; getSelectedOption(): ${enumValues.attribute}_value | null; getValue(): ${enumName} | null; setValue(value: ${enumName} | null): void; } ` } outputTS += ` declare namespace Xrm { namespace Collection { interface ItemCollection<T> { ` for (const [possibleTypeName, possibleTypesArray] of Object.entries(typeInfo.possibleTypes)) { let possibleTypeTemplate = ""; for (const possibleType of possibleTypesArray) { possibleTypeTemplate += ` TSubType extends ${possibleType} ? ${possibleType} :`; } outputTS += `\nget<TSubType extends T>(itemName: "${possibleTypeName}"):${possibleTypeTemplate} never;\n`; } outputTS += ` } }` outputTS += ` interface FormContext { `; for (const [attributeName, attributeType] of Object.entries(typeInfo.attributes)) { outputTS += `\ngetAttribute(attributeName: "${attributeName}"): ${attributeType};\n`; } for (const [controlName, controlType] of Object.entries(typeInfo.controls)) { outputTS += `\ngetControl(controlName: "${controlName}"): ${controlType};\n`; } outputTS += ` } } `; // Create a new window with a textarea showing the output. // The textarea is set to readonly to prevent editing. const w = window.open('', '_blank', 'width=600,height=400,menubar=no,toolbar=no,location=no,resizable=yes'); if (w) { w.document.write('<html><head><title>TypeScript Overload Signatures</title></head><body>'); w.document.write('<textarea readonly style="width:100%; height:90%;">' + outputTS + '</textarea>'); w.document.write('</body></html>'); w.document.close(); } else { // Fallback to prompt if popups are blocked. prompt("Copy the TypeScript definition:", outputTS); } }); })();