@mohsinonxrm/pptb-fetchxml-studio 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/dist/assets/index-DD9fzkbz.js +232 -0
- package/dist/index.html +70 -0
- package/index.html +0 -1
- package/npm-shrinkwrap.json +6329 -0
- package/package.json +95 -51
- package/CHANGELOG.md +0 -120
- package/eslint.config.js +0 -23
- package/public/vite.svg +0 -1
- package/src/app/AppShell.tsx +0 -1733
- package/src/assets/20px-Microsoft_Office_Excel_(2025/342/200/223present).svg.png +0 -0
- package/src/assets/react.svg +0 -1
- package/src/features/fetchxml/api/dataverseMetadata.ts +0 -192
- package/src/features/fetchxml/api/excelExport.ts +0 -318
- package/src/features/fetchxml/api/formattedValues.ts +0 -107
- package/src/features/fetchxml/api/pptbClient.ts +0 -2635
- package/src/features/fetchxml/model/displaySettings.ts +0 -18
- package/src/features/fetchxml/model/fetchxml.ts +0 -346
- package/src/features/fetchxml/model/fetchxmlIntellisense.ts +0 -583
- package/src/features/fetchxml/model/fetchxmlParser.ts +0 -708
- package/src/features/fetchxml/model/layoutxml.ts +0 -372
- package/src/features/fetchxml/model/nodes.ts +0 -255
- package/src/features/fetchxml/model/operators.ts +0 -624
- package/src/features/fetchxml/model/treeUtils.ts +0 -174
- package/src/features/fetchxml/state/builderStore.tsx +0 -1160
- package/src/features/fetchxml/state/cache.ts +0 -393
- package/src/features/fetchxml/ui/Dialogs/BulkDeleteDialog.tsx +0 -352
- package/src/features/fetchxml/ui/Dialogs/DeleteConfirmDialog.tsx +0 -396
- package/src/features/fetchxml/ui/Dialogs/SaveViewDialog.tsx +0 -612
- package/src/features/fetchxml/ui/Dialogs/SolutionPicker.tsx +0 -114
- package/src/features/fetchxml/ui/Dialogs/WorkflowPickerDialog.tsx +0 -437
- package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/AttributeEditor.tsx +0 -214
- package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/ConditionEditor.tsx +0 -533
- package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/FetchOptionsEditor.tsx +0 -296
- package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/FilterEditor.tsx +0 -106
- package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/LinkEntityEditor.tsx +0 -433
- package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/OrderEditor.tsx +0 -161
- package/src/features/fetchxml/ui/LeftPane/PropertiesPanel/editors/ValueOfPicker.tsx +0 -194
- package/src/features/fetchxml/ui/LeftPane/PropertiesPanel.tsx +0 -225
- package/src/features/fetchxml/ui/LeftPane/TreeView.tsx +0 -604
- package/src/features/fetchxml/ui/RightPane/AddColumnsPanel.tsx +0 -800
- package/src/features/fetchxml/ui/RightPane/ColumnConfigDialog.tsx +0 -170
- package/src/features/fetchxml/ui/RightPane/DataGridCellRenderers.tsx +0 -356
- package/src/features/fetchxml/ui/RightPane/EditColumnsPanel.tsx +0 -372
- package/src/features/fetchxml/ui/RightPane/FetchXmlEditor.tsx +0 -399
- package/src/features/fetchxml/ui/RightPane/FormattedValueUtils.ts +0 -189
- package/src/features/fetchxml/ui/RightPane/LayoutXmlViewer.tsx +0 -171
- package/src/features/fetchxml/ui/RightPane/PreviewTabs.tsx +0 -471
- package/src/features/fetchxml/ui/RightPane/ResultsCommandBar.tsx +0 -430
- package/src/features/fetchxml/ui/RightPane/ResultsGrid.tsx +0 -983
- package/src/features/fetchxml/ui/RightPane/XmlTextArea.tsx +0 -65
- package/src/features/fetchxml/ui/Settings/SettingsDrawer.tsx +0 -175
- package/src/features/fetchxml/ui/Toolbar/EntitySelector.tsx +0 -788
- package/src/features/fetchxml/ui/Toolbar/LoadViewPicker.tsx +0 -274
- package/src/features/fetchxml/ui/Toolbar/SaveViewButton.tsx +0 -174
- package/src/main.tsx +0 -11
- package/src/shared/components/AttributePicker.tsx +0 -138
- package/src/shared/components/BetweenInputPicker.tsx +0 -120
- package/src/shared/components/BooleanValuePicker.tsx +0 -165
- package/src/shared/components/DateTimeInputPicker.tsx +0 -170
- package/src/shared/components/DynamicValueList.tsx +0 -128
- package/src/shared/components/ExcelIcon.tsx +0 -30
- package/src/shared/components/MultiValuePicker.tsx +0 -178
- package/src/shared/components/NumericInputPicker.tsx +0 -156
- package/src/shared/components/OptionSetValuePicker.tsx +0 -150
- package/src/shared/components/RelationshipPicker.tsx +0 -268
- package/src/shared/contexts/ThemeContext.tsx +0 -19
- package/src/shared/hooks/useAccessMode.ts +0 -77
- package/src/shared/hooks/useLazyMetadata.ts +0 -132
- package/src/shared/hooks/usePptbContext.ts +0 -170
- package/src/shared/hooks/usePublisherFilter.ts +0 -214
- package/src/shared/hooks/useSolutionFilter.ts +0 -162
- package/src/shared/utils/debug.ts +0 -87
- package/tsconfig.app.json +0 -28
- package/tsconfig.json +0 -7
- package/tsconfig.node.json +0 -26
- package/vite.config.ts +0 -7
|
@@ -1,583 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FetchXML Intellisense Provider for Monaco Editor
|
|
3
|
-
* Provides autocomplete suggestions for FetchXML elements, attributes, and values
|
|
4
|
-
* Based on the official Fetch.xsd schema
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type * as Monaco from "monaco-editor";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Attribute definition types
|
|
11
|
-
*/
|
|
12
|
-
interface AttributeDef {
|
|
13
|
-
description: string;
|
|
14
|
-
type?: "boolean" | "integer" | string;
|
|
15
|
-
values?: string[];
|
|
16
|
-
required?: boolean;
|
|
17
|
-
default?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface ElementDef {
|
|
21
|
-
description: string;
|
|
22
|
-
attributes: Record<string, AttributeDef>;
|
|
23
|
-
children: string[];
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* FetchXML element definitions with their allowed attributes and children
|
|
28
|
-
* Derived from Fetch.xsd schema
|
|
29
|
-
*/
|
|
30
|
-
const FETCHXML_SCHEMA: { elements: Record<string, ElementDef> } = {
|
|
31
|
-
elements: {
|
|
32
|
-
fetch: {
|
|
33
|
-
description: "Root element for a FetchXML query",
|
|
34
|
-
attributes: {
|
|
35
|
-
version: { description: "FetchXML version" },
|
|
36
|
-
count: { description: "Number of records per page", type: "integer" },
|
|
37
|
-
page: { description: "Page number to retrieve", type: "integer" },
|
|
38
|
-
"paging-cookie": { description: "Paging cookie for subsequent pages" },
|
|
39
|
-
"utc-offset": { description: "UTC offset in minutes", type: "integer" },
|
|
40
|
-
aggregate: { description: "Enable aggregate queries", type: "boolean" },
|
|
41
|
-
distinct: { description: "Return only distinct records", type: "boolean" },
|
|
42
|
-
top: { description: "Maximum number of records to return", type: "integer" },
|
|
43
|
-
mapping: { description: "Data mapping type", values: ["internal", "logical"] },
|
|
44
|
-
"min-active-row-version": { description: "Minimum active row version", type: "boolean" },
|
|
45
|
-
"output-format": {
|
|
46
|
-
description: "Output format",
|
|
47
|
-
values: ["xml-ado", "xml-auto", "xml-elements", "xml-raw", "xml-platform"],
|
|
48
|
-
},
|
|
49
|
-
returntotalrecordcount: { description: "Return total record count", type: "boolean" },
|
|
50
|
-
"no-lock": { description: "Disable row locking", type: "boolean" },
|
|
51
|
-
},
|
|
52
|
-
children: ["entity", "order"],
|
|
53
|
-
},
|
|
54
|
-
entity: {
|
|
55
|
-
description: "Primary entity for the query",
|
|
56
|
-
attributes: {
|
|
57
|
-
name: { description: "Logical name of the entity", required: true },
|
|
58
|
-
enableprefiltering: { description: "Enable pre-filtering for reports", type: "boolean" },
|
|
59
|
-
prefilterparametername: { description: "Pre-filter parameter name" },
|
|
60
|
-
},
|
|
61
|
-
children: ["all-attributes", "attribute", "order", "filter", "link-entity"],
|
|
62
|
-
},
|
|
63
|
-
"link-entity": {
|
|
64
|
-
description: "Join to a related entity",
|
|
65
|
-
attributes: {
|
|
66
|
-
name: { description: "Logical name of the linked entity", required: true },
|
|
67
|
-
from: { description: "Attribute on the linked entity to join from" },
|
|
68
|
-
to: { description: "Attribute on the parent entity to join to" },
|
|
69
|
-
alias: { description: "Alias for the linked entity" },
|
|
70
|
-
"link-type": {
|
|
71
|
-
description: "Type of join",
|
|
72
|
-
values: [
|
|
73
|
-
"inner",
|
|
74
|
-
"outer",
|
|
75
|
-
"any",
|
|
76
|
-
"not any",
|
|
77
|
-
"all",
|
|
78
|
-
"not all",
|
|
79
|
-
"exists",
|
|
80
|
-
"in",
|
|
81
|
-
"matchfirstrowusingcrossapply",
|
|
82
|
-
],
|
|
83
|
-
},
|
|
84
|
-
visible: { description: "Include in results", type: "boolean" },
|
|
85
|
-
intersect: { description: "Used for N:N relationships", type: "boolean" },
|
|
86
|
-
enableprefiltering: { description: "Enable pre-filtering for reports", type: "boolean" },
|
|
87
|
-
prefilterparametername: { description: "Pre-filter parameter name" },
|
|
88
|
-
},
|
|
89
|
-
children: ["all-attributes", "attribute", "order", "filter", "link-entity"],
|
|
90
|
-
},
|
|
91
|
-
"all-attributes": {
|
|
92
|
-
description: "Select all attributes from the entity",
|
|
93
|
-
attributes: {},
|
|
94
|
-
children: [],
|
|
95
|
-
},
|
|
96
|
-
attribute: {
|
|
97
|
-
description: "Select a specific attribute",
|
|
98
|
-
attributes: {
|
|
99
|
-
name: { description: "Logical name of the attribute", required: true },
|
|
100
|
-
alias: { description: "Alias for the attribute in results" },
|
|
101
|
-
aggregate: {
|
|
102
|
-
description: "Aggregate function",
|
|
103
|
-
values: ["count", "countcolumn", "sum", "avg", "min", "max"],
|
|
104
|
-
},
|
|
105
|
-
groupby: { description: "Group by this attribute", type: "boolean" },
|
|
106
|
-
dategrouping: {
|
|
107
|
-
description: "Date grouping",
|
|
108
|
-
values: ["day", "week", "month", "quarter", "year", "fiscal-period", "fiscal-year"],
|
|
109
|
-
},
|
|
110
|
-
usertimezone: { description: "Use user timezone", type: "boolean" },
|
|
111
|
-
distinct: { description: "Distinct values only", type: "boolean" },
|
|
112
|
-
},
|
|
113
|
-
children: [],
|
|
114
|
-
},
|
|
115
|
-
order: {
|
|
116
|
-
description: "Sort order specification",
|
|
117
|
-
attributes: {
|
|
118
|
-
attribute: { description: "Attribute to sort by" },
|
|
119
|
-
alias: { description: "Alias of attribute to sort by (for aggregates)" },
|
|
120
|
-
descending: { description: "Sort in descending order", type: "boolean" },
|
|
121
|
-
},
|
|
122
|
-
children: [],
|
|
123
|
-
},
|
|
124
|
-
filter: {
|
|
125
|
-
description: "Filter criteria (WHERE clause)",
|
|
126
|
-
attributes: {
|
|
127
|
-
type: { description: "Logical operator", values: ["and", "or"], default: "and" },
|
|
128
|
-
isquickfindfields: { description: "Quick find filter", type: "boolean" },
|
|
129
|
-
},
|
|
130
|
-
children: ["condition", "filter", "link-entity"],
|
|
131
|
-
},
|
|
132
|
-
condition: {
|
|
133
|
-
description: "Filter condition",
|
|
134
|
-
attributes: {
|
|
135
|
-
attribute: { description: "Attribute to filter on", required: true },
|
|
136
|
-
operator: {
|
|
137
|
-
description: "Comparison operator",
|
|
138
|
-
required: true,
|
|
139
|
-
values: [
|
|
140
|
-
// Comparison
|
|
141
|
-
"eq",
|
|
142
|
-
"ne",
|
|
143
|
-
"neq",
|
|
144
|
-
"gt",
|
|
145
|
-
"ge",
|
|
146
|
-
"lt",
|
|
147
|
-
"le",
|
|
148
|
-
// String
|
|
149
|
-
"like",
|
|
150
|
-
"not-like",
|
|
151
|
-
"begins-with",
|
|
152
|
-
"not-begin-with",
|
|
153
|
-
"ends-with",
|
|
154
|
-
"not-end-with",
|
|
155
|
-
// Collection
|
|
156
|
-
"in",
|
|
157
|
-
"not-in",
|
|
158
|
-
"between",
|
|
159
|
-
"not-between",
|
|
160
|
-
"contain-values",
|
|
161
|
-
"not-contain-values",
|
|
162
|
-
// Null
|
|
163
|
-
"null",
|
|
164
|
-
"not-null",
|
|
165
|
-
// Date relative
|
|
166
|
-
"yesterday",
|
|
167
|
-
"today",
|
|
168
|
-
"tomorrow",
|
|
169
|
-
"last-seven-days",
|
|
170
|
-
"next-seven-days",
|
|
171
|
-
"last-week",
|
|
172
|
-
"this-week",
|
|
173
|
-
"next-week",
|
|
174
|
-
"last-month",
|
|
175
|
-
"this-month",
|
|
176
|
-
"next-month",
|
|
177
|
-
"last-year",
|
|
178
|
-
"this-year",
|
|
179
|
-
"next-year",
|
|
180
|
-
"on",
|
|
181
|
-
"on-or-before",
|
|
182
|
-
"on-or-after",
|
|
183
|
-
// Date X
|
|
184
|
-
"last-x-hours",
|
|
185
|
-
"next-x-hours",
|
|
186
|
-
"last-x-days",
|
|
187
|
-
"next-x-days",
|
|
188
|
-
"last-x-weeks",
|
|
189
|
-
"next-x-weeks",
|
|
190
|
-
"last-x-months",
|
|
191
|
-
"next-x-months",
|
|
192
|
-
"last-x-years",
|
|
193
|
-
"next-x-years",
|
|
194
|
-
"olderthan-x-minutes",
|
|
195
|
-
"olderthan-x-hours",
|
|
196
|
-
"olderthan-x-days",
|
|
197
|
-
"olderthan-x-weeks",
|
|
198
|
-
"olderthan-x-months",
|
|
199
|
-
"olderthan-x-years",
|
|
200
|
-
// Fiscal
|
|
201
|
-
"this-fiscal-year",
|
|
202
|
-
"this-fiscal-period",
|
|
203
|
-
"next-fiscal-year",
|
|
204
|
-
"next-fiscal-period",
|
|
205
|
-
"last-fiscal-year",
|
|
206
|
-
"last-fiscal-period",
|
|
207
|
-
"last-x-fiscal-years",
|
|
208
|
-
"last-x-fiscal-periods",
|
|
209
|
-
"next-x-fiscal-years",
|
|
210
|
-
"next-x-fiscal-periods",
|
|
211
|
-
"in-fiscal-year",
|
|
212
|
-
"in-fiscal-period",
|
|
213
|
-
"in-fiscal-period-and-year",
|
|
214
|
-
"in-or-before-fiscal-period-and-year",
|
|
215
|
-
"in-or-after-fiscal-period-and-year",
|
|
216
|
-
// User/Business
|
|
217
|
-
"eq-userid",
|
|
218
|
-
"ne-userid",
|
|
219
|
-
"eq-userteams",
|
|
220
|
-
"eq-useroruserteams",
|
|
221
|
-
"eq-useroruserhierarchy",
|
|
222
|
-
"eq-useroruserhierarchyandteams",
|
|
223
|
-
"eq-businessid",
|
|
224
|
-
"ne-businessid",
|
|
225
|
-
"eq-userlanguage",
|
|
226
|
-
// Hierarchy
|
|
227
|
-
"under",
|
|
228
|
-
"eq-or-under",
|
|
229
|
-
"not-under",
|
|
230
|
-
"above",
|
|
231
|
-
"eq-or-above",
|
|
232
|
-
],
|
|
233
|
-
},
|
|
234
|
-
value: { description: "Value to compare against" },
|
|
235
|
-
valueof: { description: "Compare to another attribute value" },
|
|
236
|
-
entityname: { description: "Entity alias for link-entity conditions" },
|
|
237
|
-
aggregate: {
|
|
238
|
-
description: "Aggregate function for HAVING clause",
|
|
239
|
-
values: ["count", "countcolumn", "sum", "avg", "min", "max"],
|
|
240
|
-
},
|
|
241
|
-
alias: { description: "Alias for the condition" },
|
|
242
|
-
uiname: { description: "UI display name" },
|
|
243
|
-
uitype: { description: "UI type" },
|
|
244
|
-
uihidden: { description: "Hide in UI", values: ["0", "1"] },
|
|
245
|
-
},
|
|
246
|
-
children: ["value"],
|
|
247
|
-
},
|
|
248
|
-
value: {
|
|
249
|
-
description: "Value element for multi-value operators (in, not-in, etc.)",
|
|
250
|
-
attributes: {
|
|
251
|
-
uiname: { description: "UI display name" },
|
|
252
|
-
uitype: { description: "UI type" },
|
|
253
|
-
},
|
|
254
|
-
children: [],
|
|
255
|
-
},
|
|
256
|
-
},
|
|
257
|
-
} as const;
|
|
258
|
-
|
|
259
|
-
type ElementName = keyof typeof FETCHXML_SCHEMA.elements;
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Get the current element context from cursor position
|
|
263
|
-
*/
|
|
264
|
-
function getElementContext(
|
|
265
|
-
model: Monaco.editor.ITextModel,
|
|
266
|
-
position: Monaco.Position
|
|
267
|
-
): {
|
|
268
|
-
element: string | null;
|
|
269
|
-
inAttribute: boolean;
|
|
270
|
-
attributeName: string | null;
|
|
271
|
-
inValue: boolean;
|
|
272
|
-
} {
|
|
273
|
-
const textUntilPosition = model.getValueInRange({
|
|
274
|
-
startLineNumber: 1,
|
|
275
|
-
startColumn: 1,
|
|
276
|
-
endLineNumber: position.lineNumber,
|
|
277
|
-
endColumn: position.column,
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
// Find the last opened element
|
|
281
|
-
const elementMatches = textUntilPosition.match(/<(\/?)([\w-]+)(?:\s|>|$)/g);
|
|
282
|
-
const openElements: string[] = [];
|
|
283
|
-
|
|
284
|
-
if (elementMatches) {
|
|
285
|
-
for (const match of elementMatches) {
|
|
286
|
-
const isClosing = match.startsWith("</");
|
|
287
|
-
const elementName = match.replace(/<\/?/, "").replace(/[\s>].*/, "");
|
|
288
|
-
|
|
289
|
-
if (isClosing) {
|
|
290
|
-
// Pop the last matching open element
|
|
291
|
-
const idx = openElements.lastIndexOf(elementName);
|
|
292
|
-
if (idx !== -1) {
|
|
293
|
-
openElements.splice(idx, 1);
|
|
294
|
-
}
|
|
295
|
-
} else if (!match.includes("/>")) {
|
|
296
|
-
openElements.push(elementName);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
const currentElement = openElements[openElements.length - 1] || null;
|
|
302
|
-
|
|
303
|
-
// Check if we're inside an attribute value
|
|
304
|
-
const lineText = model.getLineContent(position.lineNumber);
|
|
305
|
-
const textBeforeCursor = lineText.substring(0, position.column - 1);
|
|
306
|
-
|
|
307
|
-
// Check if inside attribute value (after = and inside quotes)
|
|
308
|
-
const attrValueMatch = textBeforeCursor.match(/(\w+)\s*=\s*["']([^"']*)$/);
|
|
309
|
-
if (attrValueMatch) {
|
|
310
|
-
return {
|
|
311
|
-
element: currentElement,
|
|
312
|
-
inAttribute: true,
|
|
313
|
-
attributeName: attrValueMatch[1],
|
|
314
|
-
inValue: true,
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// Check if we're in an opening tag (for attribute suggestions)
|
|
319
|
-
const inOpeningTag = /<[\w-]+[^>]*$/.test(textBeforeCursor);
|
|
320
|
-
|
|
321
|
-
return {
|
|
322
|
-
element: currentElement,
|
|
323
|
-
inAttribute: inOpeningTag,
|
|
324
|
-
attributeName: null,
|
|
325
|
-
inValue: false,
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* Create Monaco completion items for FetchXML
|
|
331
|
-
*/
|
|
332
|
-
function createCompletionItem(
|
|
333
|
-
label: string,
|
|
334
|
-
kind: Monaco.languages.CompletionItemKind,
|
|
335
|
-
detail: string,
|
|
336
|
-
insertText: string,
|
|
337
|
-
range: Monaco.IRange,
|
|
338
|
-
documentation?: string
|
|
339
|
-
): Monaco.languages.CompletionItem {
|
|
340
|
-
return {
|
|
341
|
-
label,
|
|
342
|
-
kind,
|
|
343
|
-
detail,
|
|
344
|
-
insertText,
|
|
345
|
-
range,
|
|
346
|
-
documentation,
|
|
347
|
-
insertTextRules: insertText.includes("$") ? 4 : undefined, // InsertAsSnippet if contains $
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
/**
|
|
352
|
-
* Register FetchXML completion provider with Monaco
|
|
353
|
-
*/
|
|
354
|
-
export function registerFetchXmlIntellisense(monaco: typeof Monaco): Monaco.IDisposable {
|
|
355
|
-
return monaco.languages.registerCompletionItemProvider("xml", {
|
|
356
|
-
triggerCharacters: ["<", " ", "=", '"', "'"],
|
|
357
|
-
|
|
358
|
-
provideCompletionItems(
|
|
359
|
-
model: Monaco.editor.ITextModel,
|
|
360
|
-
position: Monaco.Position
|
|
361
|
-
): Monaco.languages.ProviderResult<Monaco.languages.CompletionList> {
|
|
362
|
-
const word = model.getWordUntilPosition(position);
|
|
363
|
-
const range: Monaco.IRange = {
|
|
364
|
-
startLineNumber: position.lineNumber,
|
|
365
|
-
startColumn: word.startColumn,
|
|
366
|
-
endLineNumber: position.lineNumber,
|
|
367
|
-
endColumn: word.endColumn,
|
|
368
|
-
};
|
|
369
|
-
|
|
370
|
-
const context = getElementContext(model, position);
|
|
371
|
-
const lineText = model.getLineContent(position.lineNumber);
|
|
372
|
-
const textBeforeCursor = lineText.substring(0, position.column - 1);
|
|
373
|
-
|
|
374
|
-
const suggestions: Monaco.languages.CompletionItem[] = [];
|
|
375
|
-
|
|
376
|
-
// Inside attribute value - suggest valid values
|
|
377
|
-
if (context.inValue && context.attributeName && context.element) {
|
|
378
|
-
const elementDef = FETCHXML_SCHEMA.elements[context.element as ElementName];
|
|
379
|
-
if (elementDef) {
|
|
380
|
-
const attrDef =
|
|
381
|
-
elementDef.attributes[context.attributeName as keyof typeof elementDef.attributes];
|
|
382
|
-
if (attrDef && "values" in attrDef && attrDef.values) {
|
|
383
|
-
for (const value of attrDef.values) {
|
|
384
|
-
suggestions.push(
|
|
385
|
-
createCompletionItem(
|
|
386
|
-
value,
|
|
387
|
-
monaco.languages.CompletionItemKind.Value,
|
|
388
|
-
`Value for ${context.attributeName}`,
|
|
389
|
-
value,
|
|
390
|
-
range
|
|
391
|
-
)
|
|
392
|
-
);
|
|
393
|
-
}
|
|
394
|
-
} else if (attrDef && "type" in attrDef && attrDef.type === "boolean") {
|
|
395
|
-
suggestions.push(
|
|
396
|
-
createCompletionItem(
|
|
397
|
-
"true",
|
|
398
|
-
monaco.languages.CompletionItemKind.Value,
|
|
399
|
-
"Boolean true",
|
|
400
|
-
"true",
|
|
401
|
-
range
|
|
402
|
-
)
|
|
403
|
-
);
|
|
404
|
-
suggestions.push(
|
|
405
|
-
createCompletionItem(
|
|
406
|
-
"false",
|
|
407
|
-
monaco.languages.CompletionItemKind.Value,
|
|
408
|
-
"Boolean false",
|
|
409
|
-
"false",
|
|
410
|
-
range
|
|
411
|
-
)
|
|
412
|
-
);
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
return { suggestions };
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// Inside opening tag - suggest attributes
|
|
419
|
-
if (context.inAttribute && context.element && !context.inValue) {
|
|
420
|
-
const elementDef = FETCHXML_SCHEMA.elements[context.element as ElementName];
|
|
421
|
-
if (elementDef) {
|
|
422
|
-
for (const [attrName, attrDef] of Object.entries(elementDef.attributes)) {
|
|
423
|
-
// Check if attribute already exists on this line
|
|
424
|
-
if (lineText.includes(`${attrName}=`)) continue;
|
|
425
|
-
|
|
426
|
-
const required = "required" in attrDef && attrDef.required;
|
|
427
|
-
const hasValues = "values" in attrDef && attrDef.values;
|
|
428
|
-
|
|
429
|
-
suggestions.push(
|
|
430
|
-
createCompletionItem(
|
|
431
|
-
attrName,
|
|
432
|
-
monaco.languages.CompletionItemKind.Property,
|
|
433
|
-
required ? "(required)" : "(optional)",
|
|
434
|
-
hasValues ? `${attrName}="$1"` : `${attrName}="$1"`,
|
|
435
|
-
range,
|
|
436
|
-
attrDef.description
|
|
437
|
-
)
|
|
438
|
-
);
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
return { suggestions };
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
// After < - suggest elements
|
|
445
|
-
if (textBeforeCursor.endsWith("<") || textBeforeCursor.match(/<\w*$/)) {
|
|
446
|
-
// Determine valid child elements based on parent
|
|
447
|
-
let validElements: string[] = [];
|
|
448
|
-
|
|
449
|
-
if (!context.element) {
|
|
450
|
-
// Root level - only fetch is valid
|
|
451
|
-
validElements = ["fetch"];
|
|
452
|
-
} else {
|
|
453
|
-
const parentDef = FETCHXML_SCHEMA.elements[context.element as ElementName];
|
|
454
|
-
if (parentDef) {
|
|
455
|
-
validElements = [...parentDef.children];
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// Add closing tag suggestion if in an element
|
|
460
|
-
if (context.element) {
|
|
461
|
-
suggestions.push(
|
|
462
|
-
createCompletionItem(
|
|
463
|
-
`/${context.element}`,
|
|
464
|
-
monaco.languages.CompletionItemKind.Keyword,
|
|
465
|
-
`Close <${context.element}> tag`,
|
|
466
|
-
`/${context.element}>`,
|
|
467
|
-
range
|
|
468
|
-
)
|
|
469
|
-
);
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
for (const elemName of validElements) {
|
|
473
|
-
const elemDef = FETCHXML_SCHEMA.elements[elemName as ElementName];
|
|
474
|
-
if (elemDef) {
|
|
475
|
-
// Find required attributes
|
|
476
|
-
const requiredAttrs = Object.entries(elemDef.attributes)
|
|
477
|
-
.filter(([, def]) => "required" in def && def.required)
|
|
478
|
-
.map(([name]) => name);
|
|
479
|
-
|
|
480
|
-
// Create snippet with required attributes
|
|
481
|
-
let snippet = elemName;
|
|
482
|
-
let tabStop = 1;
|
|
483
|
-
for (const attr of requiredAttrs) {
|
|
484
|
-
snippet += ` ${attr}="$${tabStop++}"`;
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
// Self-closing if no children, otherwise add closing tag
|
|
488
|
-
if (elemDef.children.length === 0) {
|
|
489
|
-
snippet += " />";
|
|
490
|
-
} else {
|
|
491
|
-
snippet += ">$0</" + elemName + ">";
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
suggestions.push(
|
|
495
|
-
createCompletionItem(
|
|
496
|
-
elemName,
|
|
497
|
-
monaco.languages.CompletionItemKind.Class,
|
|
498
|
-
"FetchXML element",
|
|
499
|
-
snippet,
|
|
500
|
-
range,
|
|
501
|
-
elemDef.description
|
|
502
|
-
)
|
|
503
|
-
);
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
return { suggestions };
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
return { suggestions };
|
|
511
|
-
},
|
|
512
|
-
});
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
/**
|
|
516
|
-
* Get FetchXML hover information
|
|
517
|
-
*/
|
|
518
|
-
export function registerFetchXmlHoverProvider(monaco: typeof Monaco): Monaco.IDisposable {
|
|
519
|
-
return monaco.languages.registerHoverProvider("xml", {
|
|
520
|
-
provideHover(
|
|
521
|
-
model: Monaco.editor.ITextModel,
|
|
522
|
-
position: Monaco.Position
|
|
523
|
-
): Monaco.languages.ProviderResult<Monaco.languages.Hover> {
|
|
524
|
-
const word = model.getWordAtPosition(position);
|
|
525
|
-
if (!word) return null;
|
|
526
|
-
|
|
527
|
-
const lineText = model.getLineContent(position.lineNumber);
|
|
528
|
-
|
|
529
|
-
// Check if hovering over an element name
|
|
530
|
-
const elementMatch = lineText.match(new RegExp(`<(/?)(${word.word})(?:\\s|>|/)`));
|
|
531
|
-
if (elementMatch) {
|
|
532
|
-
const elemDef = FETCHXML_SCHEMA.elements[word.word as ElementName];
|
|
533
|
-
if (elemDef) {
|
|
534
|
-
return {
|
|
535
|
-
contents: [
|
|
536
|
-
{ value: `**<${word.word}>**` },
|
|
537
|
-
{ value: elemDef.description },
|
|
538
|
-
{
|
|
539
|
-
value:
|
|
540
|
-
elemDef.children.length > 0
|
|
541
|
-
? `\n**Child elements:** ${elemDef.children.join(", ")}`
|
|
542
|
-
: "\n*Self-closing element*",
|
|
543
|
-
},
|
|
544
|
-
],
|
|
545
|
-
};
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
// Check if hovering over an attribute name
|
|
550
|
-
const attrMatch = lineText.match(new RegExp(`(${word.word})\\s*=`));
|
|
551
|
-
if (attrMatch) {
|
|
552
|
-
// Find parent element
|
|
553
|
-
const context = getElementContext(model, position);
|
|
554
|
-
if (context.element) {
|
|
555
|
-
const elemDef = FETCHXML_SCHEMA.elements[context.element as ElementName];
|
|
556
|
-
if (elemDef) {
|
|
557
|
-
const attrDef = elemDef.attributes[word.word as keyof typeof elemDef.attributes];
|
|
558
|
-
if (attrDef) {
|
|
559
|
-
const contents: Monaco.IMarkdownString[] = [
|
|
560
|
-
{ value: `**${word.word}**` },
|
|
561
|
-
{ value: attrDef.description },
|
|
562
|
-
];
|
|
563
|
-
|
|
564
|
-
if ("values" in attrDef && attrDef.values) {
|
|
565
|
-
contents.push({ value: `\n**Valid values:** ${attrDef.values.join(", ")}` });
|
|
566
|
-
}
|
|
567
|
-
if ("type" in attrDef && attrDef.type) {
|
|
568
|
-
contents.push({ value: `\n**Type:** ${attrDef.type}` });
|
|
569
|
-
}
|
|
570
|
-
if ("required" in attrDef && attrDef.required) {
|
|
571
|
-
contents.push({ value: "\n*Required attribute*" });
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
return { contents };
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
return null;
|
|
581
|
-
},
|
|
582
|
-
});
|
|
583
|
-
}
|