intelligent-system-design-language 0.3.21 → 0.3.23

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.
Files changed (116) hide show
  1. package/.claude/agents/langium-language-designer.md +38 -38
  2. package/.claude/agents/typescript-vscode-expert.md +29 -29
  3. package/.claude/agents/ui-ux-designer.md +36 -36
  4. package/.claude/settings.local.json +33 -33
  5. package/.idea/inspectionProfiles/Project_Default.xml +6 -6
  6. package/.idea/isdl.iml +13 -13
  7. package/.idea/modules.xml +8 -8
  8. package/.idea/vcs.xml +6 -6
  9. package/.idea/watcherTasks.xml +3 -3
  10. package/.vscodeignore +18 -18
  11. package/LICENSE +673 -673
  12. package/README.md +86 -86
  13. package/bin/cli.js +4 -4
  14. package/bin/lsp.js +8 -8
  15. package/out/_backgrounds.scss +91 -91
  16. package/out/_handlebars.scss +497 -497
  17. package/out/_isdlStyles.scss +1444 -1381
  18. package/out/_vuetifyOverrides.scss +425 -425
  19. package/out/_vuetifyStyles.scss +31957 -31957
  20. package/out/cli/components/_backgrounds.scss +91 -91
  21. package/out/cli/components/_handlebars.scss +497 -497
  22. package/out/cli/components/_isdlStyles.scss +1444 -1381
  23. package/out/cli/components/_vuetifyOverrides.scss +425 -425
  24. package/out/cli/components/_vuetifyStyles.scss +31957 -31957
  25. package/out/cli/components/active-effect-sheet-generator.js +453 -453
  26. package/out/cli/components/chat-card-generator.js +654 -651
  27. package/out/cli/components/chat-card-generator.js.map +1 -1
  28. package/out/cli/components/css-generator.js +4 -4
  29. package/out/cli/components/damage-roll-generator.js +160 -160
  30. package/out/cli/components/datamodel-generator.js +264 -257
  31. package/out/cli/components/datamodel-generator.js.map +1 -1
  32. package/out/cli/components/derived-data-generator.js +923 -923
  33. package/out/cli/components/hotbar-drop-hook-generator.js +82 -82
  34. package/out/cli/components/init-hook-generator.js +495 -495
  35. package/out/cli/components/language-generator.js +1 -1
  36. package/out/cli/components/language-generator.js.map +1 -1
  37. package/out/cli/components/measured-template-preview.js +221 -221
  38. package/out/cli/components/method-generator.js +979 -887
  39. package/out/cli/components/method-generator.js.map +1 -1
  40. package/out/cli/components/ready-hook-generator.js +404 -404
  41. package/out/cli/components/token-generator.js +116 -116
  42. package/out/cli/components/vue/base-components/vue-attribute.js +138 -138
  43. package/out/cli/components/vue/base-components/vue-boolean.js +64 -64
  44. package/out/cli/components/vue/base-components/vue-calculator.js +93 -93
  45. package/out/cli/components/vue/base-components/vue-damage-application.js +356 -356
  46. package/out/cli/components/vue/base-components/vue-damage-bonuses.js +165 -165
  47. package/out/cli/components/vue/base-components/vue-damage-resistances.js +196 -196
  48. package/out/cli/components/vue/base-components/vue-damage-track.js +121 -121
  49. package/out/cli/components/vue/base-components/vue-date-time.js +42 -42
  50. package/out/cli/components/vue/base-components/vue-dice.js +98 -98
  51. package/out/cli/components/vue/base-components/vue-die.js +73 -73
  52. package/out/cli/components/vue/base-components/vue-document-choice.js +149 -149
  53. package/out/cli/components/vue/base-components/vue-document-choices.js +179 -179
  54. package/out/cli/components/vue/base-components/vue-document-link.js +60 -60
  55. package/out/cli/components/vue/base-components/vue-extended-choice.js +88 -88
  56. package/out/cli/components/vue/base-components/vue-inventory.js +519 -519
  57. package/out/cli/components/vue/base-components/vue-macro-choice.js +138 -138
  58. package/out/cli/components/vue/base-components/vue-measured-template.js +530 -530
  59. package/out/cli/components/vue/base-components/vue-money.js +483 -483
  60. package/out/cli/components/vue/base-components/vue-number.js +174 -174
  61. package/out/cli/components/vue/base-components/vue-paperdoll.js +43 -43
  62. package/out/cli/components/vue/base-components/vue-parent-property-reference.js +76 -76
  63. package/out/cli/components/vue/base-components/vue-prosemirror.js +18 -18
  64. package/out/cli/components/vue/base-components/vue-resource.js +136 -136
  65. package/out/cli/components/vue/base-components/vue-roll-visualizer.js +286 -109
  66. package/out/cli/components/vue/base-components/vue-roll-visualizer.js.map +1 -1
  67. package/out/cli/components/vue/base-components/vue-self-property-reference.js +62 -62
  68. package/out/cli/components/vue/base-components/vue-string-choice.js +98 -98
  69. package/out/cli/components/vue/base-components/vue-string-choices.js +203 -203
  70. package/out/cli/components/vue/base-components/vue-string.js +60 -60
  71. package/out/cli/components/vue/base-components/vue-text-field.js +53 -53
  72. package/out/cli/components/vue/base-components/vue-tracker.js +431 -431
  73. package/out/cli/components/vue/vue-action-component-generator.js +64 -64
  74. package/out/cli/components/vue/vue-active-effect-sheet-generator.js +856 -856
  75. package/out/cli/components/vue/vue-datatable-sheet-class-generator.js +292 -292
  76. package/out/cli/components/vue/vue-datatable2-component-generator.js +824 -824
  77. package/out/cli/components/vue/vue-document-creation-app.js +121 -121
  78. package/out/cli/components/vue/vue-document-creation-sheet.js +94 -94
  79. package/out/cli/components/vue/vue-generator.js +40 -40
  80. package/out/cli/components/vue/vue-mixin.js +296 -296
  81. package/out/cli/components/vue/vue-pinned-datatable-component-generator.js +260 -260
  82. package/out/cli/components/vue/vue-prompt-generator.js +91 -76
  83. package/out/cli/components/vue/vue-prompt-generator.js.map +1 -1
  84. package/out/cli/components/vue/vue-prompt-sheet-class-generator.js +317 -317
  85. package/out/cli/components/vue/vue-sheet-application-generator.js +1177 -1167
  86. package/out/cli/components/vue/vue-sheet-application-generator.js.map +1 -1
  87. package/out/cli/components/vue/vue-sheet-class-generator.js +510 -510
  88. package/out/cli/generator.js +438 -433
  89. package/out/cli/generator.js.map +1 -1
  90. package/out/extension/github/githubAuthProvider.js +71 -29
  91. package/out/extension/github/githubAuthProvider.js.map +1 -1
  92. package/out/extension/github/githubGistManager.js +4 -3
  93. package/out/extension/github/githubGistManager.js.map +1 -1
  94. package/out/extension/github/githubManager.js +40 -38
  95. package/out/extension/github/githubManager.js.map +1 -1
  96. package/out/extension/github/githubQuickActions.js +120 -120
  97. package/out/extension/github/system-workflow.yml +47 -47
  98. package/out/extension/main.cjs +909 -532
  99. package/out/extension/main.cjs.map +3 -3
  100. package/out/extension/package.json +419 -419
  101. package/out/language/generated/ast.js +51 -2
  102. package/out/language/generated/ast.js.map +1 -1
  103. package/out/language/generated/grammar.js +14240 -13991
  104. package/out/language/generated/grammar.js.map +1 -1
  105. package/out/language/intelligent-system-design-language-validator.js +32 -2
  106. package/out/language/intelligent-system-design-language-validator.js.map +1 -1
  107. package/out/language/isdl-scope-provider.js +14 -1
  108. package/out/language/isdl-scope-provider.js.map +1 -1
  109. package/out/language/main.cjs +913 -569
  110. package/out/language/main.cjs.map +3 -3
  111. package/out/package.json +419 -419
  112. package/out/progressbar.min.js +6 -6
  113. package/out/styles.scss +762 -747
  114. package/out/test/validating/diagnostics.test.js +40 -0
  115. package/out/test/validating/diagnostics.test.js.map +1 -1
  116. package/package.json +419 -419
@@ -108,76 +108,76 @@ function generateSystemJson(entry, id, destination) {
108
108
  const generatedFilePath = path.join(destination, `system.json`);
109
109
  // Get the version of the extension
110
110
  const extensionVersion = getExtensionVersion();
111
- const fileNode = expandToNode `
112
- {
113
- "id": "${id}",
114
- "title": "${(_a = entry.config.body.find(x => isConfigExpression(x) && x.type == "label")) === null || _a === void 0 ? void 0 : _a.value}",
115
- "description": "${(_b = entry.config.body.find(x => isConfigExpression(x) && x.type == "description")) === null || _b === void 0 ? void 0 : _b.value}",
116
- "version": "This is auto replaced",
117
- "compatibility": {
118
- "minimum": 12,
119
- "verified": 14
120
- },
121
- "authors": [
122
- {
123
- "name": "${(_c = entry.config.body.find(x => isConfigExpression(x) && x.type == "author")) === null || _c === void 0 ? void 0 : _c.value}"
124
- }
125
- ],
126
- "scripts": [
127
- ],
128
- "esmodules": [
129
- "system/${id}-main.mjs",
130
- "system/${id}-custom.mjs"
131
- ],
132
- "styles": [
133
- "css/materialdesignicons.min.css",
134
- "css/${id}.css",
135
- "css/${id}-custom.css"
136
- ],
137
- "license": "LICENSE",
138
- "readme": "README.md",
139
- "socket": true,
140
- "languages": [
141
- {
142
- "lang": "en",
143
- "name": "English",
144
- "path": "lang/en.json"
145
- }
146
- ],
147
- "flags": {
148
- "hotReload": {
149
- "extensions": ["css", "hbs", "json", "mjs"],
150
- "paths": ["css", "system", "lang", "system", "system.json"]
151
- },
152
- "isdl-version": "${extensionVersion}"
153
- },
154
- "media": [
155
- {
156
- "type": "setup",
157
- "url": "systems/${id}/img/isdl.png",
158
- "thumbnail": "systems/${id}/img/isdl.png"
159
- }
160
- ],
161
- "relationships": {
162
- "recommends": [
163
- {
164
- "id": "intelligent-filepicker",
165
- "type": "module",
166
- "manifest": "https://github.com/cswendrowski/intelligent-filepicker/releases/latest/download/module.json",
167
- "reason": "Makes it much faster to pick out Icons for your Documents"
168
- },
169
- {
170
- "id": "ric",
171
- "type": "module",
172
- "manifest": "https://github.com/GamerFlix/ric/releases/latest/download/module.json",
173
- "reason": "Adds an UI for managing invalid Documents, such as when fields change to become required but were not filled in"
174
- }
175
- ]
176
- },
177
- "url": "This is auto replaced",
178
- "manifest": "This is auto replaced",
179
- "download": "This is auto replaced"
180
- }
111
+ const fileNode = expandToNode `
112
+ {
113
+ "id": "${id}",
114
+ "title": "${(_a = entry.config.body.find(x => isConfigExpression(x) && x.type == "label")) === null || _a === void 0 ? void 0 : _a.value}",
115
+ "description": "${(_b = entry.config.body.find(x => isConfigExpression(x) && x.type == "description")) === null || _b === void 0 ? void 0 : _b.value}",
116
+ "version": "This is auto replaced",
117
+ "compatibility": {
118
+ "minimum": 12,
119
+ "verified": 14
120
+ },
121
+ "authors": [
122
+ {
123
+ "name": "${(_c = entry.config.body.find(x => isConfigExpression(x) && x.type == "author")) === null || _c === void 0 ? void 0 : _c.value}"
124
+ }
125
+ ],
126
+ "scripts": [
127
+ ],
128
+ "esmodules": [
129
+ "system/${id}-main.mjs",
130
+ "system/${id}-custom.mjs"
131
+ ],
132
+ "styles": [
133
+ "css/materialdesignicons.min.css",
134
+ "css/${id}.css",
135
+ "css/${id}-custom.css"
136
+ ],
137
+ "license": "LICENSE",
138
+ "readme": "README.md",
139
+ "socket": true,
140
+ "languages": [
141
+ {
142
+ "lang": "en",
143
+ "name": "English",
144
+ "path": "lang/en.json"
145
+ }
146
+ ],
147
+ "flags": {
148
+ "hotReload": {
149
+ "extensions": ["css", "hbs", "json", "mjs"],
150
+ "paths": ["css", "system", "lang", "system", "system.json"]
151
+ },
152
+ "isdl-version": "${extensionVersion}"
153
+ },
154
+ "media": [
155
+ {
156
+ "type": "setup",
157
+ "url": "systems/${id}/img/isdl.png",
158
+ "thumbnail": "systems/${id}/img/isdl.png"
159
+ }
160
+ ],
161
+ "relationships": {
162
+ "recommends": [
163
+ {
164
+ "id": "intelligent-filepicker",
165
+ "type": "module",
166
+ "manifest": "https://github.com/cswendrowski/intelligent-filepicker/releases/latest/download/module.json",
167
+ "reason": "Makes it much faster to pick out Icons for your Documents"
168
+ },
169
+ {
170
+ "id": "ric",
171
+ "type": "module",
172
+ "manifest": "https://github.com/GamerFlix/ric/releases/latest/download/module.json",
173
+ "reason": "Adds an UI for managing invalid Documents, such as when fields change to become required but were not filled in"
174
+ }
175
+ ]
176
+ },
177
+ "url": "This is auto replaced",
178
+ "manifest": "This is auto replaced",
179
+ "download": "This is auto replaced"
180
+ }
181
181
  `.appendNewLineIfNotEmpty();
182
182
  fs.writeFileSync(generatedFilePath, toString(fileNode));
183
183
  }
@@ -197,21 +197,21 @@ function generateTemplateJson(entry, id, destination) {
197
197
  }
198
198
  function generateEntryMjs(entry, id, destination) {
199
199
  const generatedFilePath = path.join(destination, "system", `${id}-main.mjs`);
200
- const fileNode = expandToNode `
201
- import {init} from "./hooks/init.mjs";
202
- import {ready} from "./hooks/ready.mjs";
203
- import {renderChatLog} from "./hooks/render-chat-log.mjs";
204
- import {renderSettings} from "./hooks/render-settings.mjs";
205
- import {hotReload} from "./hooks/hot-reload.mjs";
206
- import {hotbarDrop} from "./hooks/hotbar-drop.mjs";
207
-
208
- Hooks.once("init", init);
209
- Hooks.once("ready", ready);
210
- Hooks.on("devModeReady", ({registerPackageDebugFlag}) => registerPackageDebugFlag("${id}"));
211
- Hooks.on("renderChatMessage", renderChatLog);
212
- Hooks.on("renderSettings", renderSettings);
213
- Hooks.on("hotReload", hotReload);
214
- Hooks.on("hotbarDrop", hotbarDrop);
200
+ const fileNode = expandToNode `
201
+ import {init} from "./hooks/init.mjs";
202
+ import {ready} from "./hooks/ready.mjs";
203
+ import {renderChatLog} from "./hooks/render-chat-log.mjs";
204
+ import {renderSettings} from "./hooks/render-settings.mjs";
205
+ import {hotReload} from "./hooks/hot-reload.mjs";
206
+ import {hotbarDrop} from "./hooks/hotbar-drop.mjs";
207
+
208
+ Hooks.once("init", init);
209
+ Hooks.once("ready", ready);
210
+ Hooks.on("devModeReady", ({registerPackageDebugFlag}) => registerPackageDebugFlag("${id}"));
211
+ Hooks.on("renderChatMessage", renderChatLog);
212
+ Hooks.on("renderSettings", renderSettings);
213
+ Hooks.on("hotReload", hotReload);
214
+ Hooks.on("hotbarDrop", hotbarDrop);
215
215
  `.appendNewLineIfNotEmpty();
216
216
  fs.writeFileSync(generatedFilePath, toString(fileNode));
217
217
  }
@@ -221,10 +221,10 @@ function generateCustomEntryMjs(entry, id, destination) {
221
221
  if (fs.existsSync(generatedFilePath)) {
222
222
  return;
223
223
  }
224
- const fileNode = expandToNode `
225
- // Write your custom code and hooks here. This file will not be overwritten by the generator.
226
-
227
- Hooks.once("init", () => {});
224
+ const fileNode = expandToNode `
225
+ // Write your custom code and hooks here. This file will not be overwritten by the generator.
226
+
227
+ Hooks.once("init", () => {});
228
228
  `.appendNewLineIfNotEmpty();
229
229
  fs.writeFileSync(generatedFilePath, toString(fileNode));
230
230
  }
@@ -234,35 +234,35 @@ function generateHotReloadHookMjs(entry, id, destination) {
234
234
  if (!fs.existsSync(generatedFileDir)) {
235
235
  fs.mkdirSync(generatedFileDir, { recursive: true });
236
236
  }
237
- const fileNode = expandToNode `
238
- export function hotReload(context) {
239
- const reloadFileTypes = ["mjs", "json"];
240
- if (!reloadFileTypes.includes(context.extension)) return;
241
-
242
- if (context.extension === "json") {
243
- if (!context.path.endsWith("system.json")) return;
244
- ui.notifications.warn("The system configuration has been updated. Please reload your world to apply changes.", { permanent: true });
245
- }
246
-
247
- ui.notifications.info("Reloading page to apply script changes", { permanent: true });
248
-
249
- const lastState = {
250
- openWindows: []
251
- };
252
- for (const window of Object.values(ui.windows)) {
253
- if (!window.object) continue;
254
- const uuid = window.object.uuid;
255
- lastState.openWindows.push({
256
- uuid: uuid,
257
- position: window.position
258
- });
259
- }
260
- game.settings.set("${id}", "hotReloadLastState", lastState).then(() =>
261
- {
262
- // Reload the page
263
- window.location.reload(true);
264
- });
265
- }
237
+ const fileNode = expandToNode `
238
+ export function hotReload(context) {
239
+ const reloadFileTypes = ["mjs", "json"];
240
+ if (!reloadFileTypes.includes(context.extension)) return;
241
+
242
+ if (context.extension === "json") {
243
+ if (!context.path.endsWith("system.json")) return;
244
+ ui.notifications.warn("The system configuration has been updated. Please reload your world to apply changes.", { permanent: true });
245
+ }
246
+
247
+ ui.notifications.info("Reloading page to apply script changes", { permanent: true });
248
+
249
+ const lastState = {
250
+ openWindows: []
251
+ };
252
+ for (const window of Object.values(ui.windows)) {
253
+ if (!window.object) continue;
254
+ const uuid = window.object.uuid;
255
+ lastState.openWindows.push({
256
+ uuid: uuid,
257
+ position: window.position
258
+ });
259
+ }
260
+ game.settings.set("${id}", "hotReloadLastState", lastState).then(() =>
261
+ {
262
+ // Reload the page
263
+ window.location.reload(true);
264
+ });
265
+ }
266
266
  `.appendNewLineIfNotEmpty();
267
267
  fs.writeFileSync(generatedFilePath, toString(fileNode));
268
268
  }
@@ -272,12 +272,12 @@ function generateRenderChatLogHookMjs(entry, id, destination) {
272
272
  if (!fs.existsSync(generatedFileDir)) {
273
273
  fs.mkdirSync(generatedFileDir, { recursive: true });
274
274
  }
275
- const fileNode = expandToNode `
276
- import ${entry.config.name}ChatCard from "../documents/chat-card.mjs";
277
-
278
- export function renderChatLog(app, html, data) {
279
- ${entry.config.name}ChatCard.activateListeners(html);
280
- }
275
+ const fileNode = expandToNode `
276
+ import ${entry.config.name}ChatCard from "../documents/chat-card.mjs";
277
+
278
+ export function renderChatLog(app, html, data) {
279
+ ${entry.config.name}ChatCard.activateListeners(html);
280
+ }
281
281
  `.appendNewLineIfNotEmpty();
282
282
  fs.writeFileSync(generatedFilePath, toString(fileNode));
283
283
  }
@@ -287,33 +287,33 @@ function generateRenderSettingsHookMjs(entry, id, destination) {
287
287
  if (!fs.existsSync(generatedFileDir)) {
288
288
  fs.mkdirSync(generatedFileDir, { recursive: true });
289
289
  }
290
- const fileNode = expandToNode `
291
- // Adds an "ISDL" version row to the Foundry Settings sidebar, next to the
292
- // system info, showing which version of ISDL generated this system.
293
- export function renderSettings(app, html) {
294
- const root = html instanceof HTMLElement ? html : html?.[0];
295
- if (!root) return;
296
-
297
- const info = root.querySelector("section.info");
298
- if (!info || info.querySelector(".isdl-version")) return;
299
-
300
- const version = game.system.flags?.["isdl-version"] ?? "unknown";
301
-
302
- const row = document.createElement("div");
303
- row.classList.add("isdl-version");
304
- const label = document.createElement("span");
305
- label.classList.add("label");
306
- label.textContent = "ISDL";
307
- const value = document.createElement("span");
308
- value.classList.add("value");
309
- value.textContent = version;
310
- row.append(label, value);
311
-
312
- // Place it right after the system row when present, else append.
313
- const systemRow = info.querySelector(".system");
314
- if (systemRow) systemRow.after(row);
315
- else info.appendChild(row);
316
- }
290
+ const fileNode = expandToNode `
291
+ // Adds an "ISDL" version row to the Foundry Settings sidebar, next to the
292
+ // system info, showing which version of ISDL generated this system.
293
+ export function renderSettings(app, html) {
294
+ const root = html instanceof HTMLElement ? html : html?.[0];
295
+ if (!root) return;
296
+
297
+ const info = root.querySelector("section.info");
298
+ if (!info || info.querySelector(".isdl-version")) return;
299
+
300
+ const version = game.system.flags?.["isdl-version"] ?? "unknown";
301
+
302
+ const row = document.createElement("div");
303
+ row.classList.add("isdl-version");
304
+ const label = document.createElement("span");
305
+ label.classList.add("label");
306
+ label.textContent = "ISDL";
307
+ const value = document.createElement("span");
308
+ value.classList.add("value");
309
+ value.textContent = version;
310
+ row.append(label, value);
311
+
312
+ // Place it right after the system row when present, else append.
313
+ const systemRow = info.querySelector(".system");
314
+ if (systemRow) systemRow.after(row);
315
+ else info.appendChild(row);
316
+ }
317
317
  `.appendNewLineIfNotEmpty();
318
318
  fs.writeFileSync(generatedFilePath, toString(fileNode));
319
319
  }
@@ -323,263 +323,268 @@ function generateExtendedRoll(entry, id, destination) {
323
323
  if (!fs.existsSync(generatedFileDir)) {
324
324
  fs.mkdirSync(generatedFileDir, { recursive: true });
325
325
  }
326
- const fileNode = expandToNode `
327
- export default class ${entry.config.name}Roll extends Roll {
328
- async getTooltip() {
329
- const parts = [];
330
-
331
- for ( const term of this.terms ) {
332
- if ( foundry.utils.isSubclass(term.constructor, foundry.dice.terms.DiceTerm) ) {
333
- parts.push(term.getTooltipData());
334
- }
335
- else if ( foundry.utils.isSubclass(term.constructor, foundry.dice.terms.NumericTerm) ) {
336
- parts.push({
337
- formula: term.flavor,
338
- total: term.total,
339
- faces: null,
340
- flavor: "",
341
- rolls: []
342
- });
343
- }
344
- }
345
-
346
- return renderTemplate(this.constructor.TOOLTIP_TEMPLATE, { parts });
347
- }
348
-
349
- /* -------------------------------------------- */
350
-
351
- get cleanFormula() {
352
- // Replace flavor terms such as 5[STR] with just the flavor text
353
- let cleanFormula = this._formula;
354
- for ( const term of this.terms ) {
355
- if ( term.formula && term.flavor ) {
356
- cleanFormula = cleanFormula.replace(term.formula, term.flavor);
357
- }
358
- }
359
-
360
- // If there are still parts of the formula such as 5[STR] then replace them with just the flavor text
361
- const rgx = new RegExp(/(\\d+)\\[(.*?)\\]/g);
362
- cleanFormula = cleanFormula.replace(rgx, "$2");
363
-
364
- return cleanFormula;
365
- }
366
- }
326
+ const fileNode = expandToNode `
327
+ export default class ${entry.config.name}Roll extends Roll {
328
+ async getTooltip() {
329
+ const parts = [];
330
+
331
+ for ( const term of this.terms ) {
332
+ if ( foundry.utils.isSubclass(term.constructor, foundry.dice.terms.DiceTerm) ) {
333
+ parts.push(term.getTooltipData());
334
+ }
335
+ else if ( foundry.utils.isSubclass(term.constructor, foundry.dice.terms.NumericTerm) ) {
336
+ parts.push({
337
+ formula: term.flavor,
338
+ total: term.total,
339
+ faces: null,
340
+ flavor: "",
341
+ rolls: []
342
+ });
343
+ }
344
+ }
345
+
346
+ return renderTemplate(this.constructor.TOOLTIP_TEMPLATE, { parts });
347
+ }
348
+
349
+ /* -------------------------------------------- */
350
+
351
+ get cleanFormula() {
352
+ // Replace flavor terms such as 5[STR] with just the flavor text
353
+ let cleanFormula = this._formula;
354
+ for ( const term of this.terms ) {
355
+ if ( term.formula && term.flavor ) {
356
+ cleanFormula = cleanFormula.replace(term.formula, term.flavor);
357
+ }
358
+ }
359
+
360
+ // If there are still parts of the formula such as 5[STR] then replace them with just the flavor text
361
+ const rgx = new RegExp(/(\\d+)\\[(.*?)\\]/g);
362
+ cleanFormula = cleanFormula.replace(rgx, "$2");
363
+
364
+ return cleanFormula;
365
+ }
366
+ }
367
367
  `.appendNewLineIfNotEmpty();
368
368
  fs.writeFileSync(generatedFilePath, toString(fileNode));
369
369
  }
370
370
  function generateContextMenu2(entry, id, destination) {
371
371
  const generatedFilePath = path.join(destination, "system", "contextMenu2.js");
372
- const fileNode = expandToNode `
373
- export class ContextMenu2 {
374
- constructor(element, selector, menuItems, {eventName="contextmenu"}={}) {
375
-
376
- /**
377
- * The target HTMLElement being selected
378
- * @type {HTMLElement}
379
- */
380
- this.element = element;
381
-
382
- /**
383
- * The target CSS selector which activates the menu
384
- * @type {String}
385
- */
386
- this.selector = selector || element.attr("id");
387
-
388
- /**
389
- * An interaction event name which activates the menu
390
- * @type {String}
391
- */
392
- this.eventName = eventName;
393
-
394
- /**
395
- * The array of menu items being rendered
396
- * @type {Array}
397
- */
398
- this.menuItems = menuItems;
399
-
400
- /**
401
- * Track which direction the menu is expanded in
402
- * @type {Boolean}
403
- */
404
- this._expandUp = false;
405
-
406
- // Bind to the current element
407
- this.bind();
408
- }
409
-
410
- /* -------------------------------------------- */
411
-
412
- /**
413
- * A convenience accessor to the context menu HTML object
414
- * @return {*|jQuery.fn.init|jQuery|HTMLElement}
415
- */
416
- get menu() {
417
- return $("#context-menu2");
418
- }
419
-
420
- /* -------------------------------------------- */
421
-
422
- /**
423
- * Attach a ContextMenu instance to an HTML selector
424
- */
425
- bind() {
426
- this.element.on(this.eventName, this.selector, event => {
427
- event.preventDefault();
428
- event.stopPropagation();
429
- let parent = $(event.currentTarget),
430
- menu = this.menu;
431
-
432
- if (this.selector == ".message") return;
433
-
434
- // Remove existing context UI
435
- $('.context').removeClass("context");
436
-
437
- // Close the current context
438
- if ( $.contains(parent[0], menu[0]) ) this.close();
439
-
440
- // If the new target element is different
441
- else {
442
- this.render(parent);
443
- ui.context = this;
444
- }
445
- })
446
- }
447
-
448
- /* -------------------------------------------- */
449
-
450
- /**
451
- * Animate closing the menu by sliding up and removing from the DOM
452
- */
453
- async close() {
454
- let menu = this.menu;
455
- await this._animateClose(menu);
456
- menu.remove();
457
- $('.context').removeClass("context");
458
- delete ui.context;
459
- }
460
-
461
- /* -------------------------------------------- */
462
-
463
- async _animateOpen(menu) {
464
- menu.hide();
465
- return new Promise(resolve => menu.slideDown(200, resolve));
466
- }
467
-
468
- /* -------------------------------------------- */
469
-
470
- async _animateClose(menu) {
471
- return new Promise(resolve => menu.slideUp(200, resolve));
472
- }
473
-
474
- /* -------------------------------------------- */
475
-
476
- /**
477
- * Render the Context Menu by iterating over the menuItems it contains
478
- * Check the visibility of each menu item, and only render ones which are allowed by the item's logical condition
479
- * Attach a click handler to each item which is rendered
480
- * @param target
481
- */
482
- render(target) {
483
- let html = $("#context-menu2").length ? $("#context-menu2") : $('<nav id="context-menu2" data-mod="1"></nav>');
484
- let ol = $('<ol class="context-items"></ol>');
485
- html.append($(\`<h2>\${game.i18n.localize('CONTEXT.ApplyChanges')}</h2>\`));
486
- html.append(ol);
487
-
488
- // Determine if user-selected targets are allowed.
489
- const allowTargeting = game.settings.get('${id}', 'allowTargetDamageApplication');
490
- let targetType = game.settings.get('${id}', 'userTargetDamageApplicationType');
491
- if (!allowTargeting && targetType !== 'selected') {
492
- game.settings.set('${id}', 'userTargetDamageApplicationType', 'selected');
493
- targetType = 'selected';
494
- }
495
-
496
- // Add default target type.
497
- html[0].dataset.target = targetType;
498
-
499
- // Build menu items
500
- for (let item of this.menuItems) {
501
-
502
- // Determine menu item visibility (display unless false)
503
- let display = true;
504
- if ( item.condition !== undefined ) {
505
- display = ( item.condition instanceof Function ) ? item.condition(target) : item.condition;
506
- }
507
- if ( !display ) continue;
508
-
509
- // Construct and add the menu item
510
- let name = game.i18n.localize(item.name);
511
- let li = $(\`<li class="context-item \${item?.id ?? ''}">\${item.icon}\${name}</li>\`);
512
- // If this is the target buttons option, set one of them to active.
513
- if (name.includes('data-target="targeted"')) {
514
- const button = li.find(\`[data-target="\${targetType}"]\`);
515
- button.addClass('active');
516
- }
517
- li.children("i").addClass("fa-fw");
518
- li.click(e => {
519
- e.preventDefault();
520
- e.stopPropagation();
521
- item.callback(target, e);
522
- // If this was a target button, prevent closing the context menu.
523
- if (!item?.preventClose) {
524
- this.close();
525
- }
526
- });
527
- ol.append(li);
528
- }
529
-
530
- // Bail out if there are no children
531
- if ( ol.children().length === 0 ) return;
532
-
533
- // Append to target
534
- this._setPosition(html, target);
535
-
536
- // Deactivate global tooltip
537
- game.tooltip.deactivate();
538
-
539
- // Animate open the menu
540
- return this._animateOpen(html);
541
- }
542
-
543
- /* -------------------------------------------- */
544
-
545
- /**
546
- * Set the position of the context menu, taking into consideration whether the menu should expand upward or downward
547
- * @private
548
- */
549
- _setPosition(html, target) {
550
- const targetRect = target[0].getBoundingClientRect();
551
- const parentRect = target[0].parentElement.getBoundingClientRect();
552
-
553
- // Append to target and get the context bounds
554
- target.css('position', 'relative');
555
- html.css("visibility", "hidden");
556
- target.append(html);
557
- const contextRect = html[0].getBoundingClientRect();
558
-
559
- // Determine whether to expand down or expand up
560
- const bottomHalf = targetRect.bottom > (window.innerHeight / 2);
561
- this._expandUp = bottomHalf && ((parentRect.bottom - targetRect.bottom) < contextRect.height);
562
-
563
- // Shift left if needed to avoid overflowing
564
- const horizontalOverflow = parentRect.right - contextRect.right;
565
- if (horizontalOverflow < 0) {
566
- html.css("left", Math.floor(horizontalOverflow));
567
- }
568
-
569
- // Display the menu
570
- html.addClass(this._expandUp ? "expand-up" : "expand-down");
571
- html.css("visibility", "");
572
- target.addClass("context");
573
- }
574
-
575
- /* -------------------------------------------- */
576
-
577
- static eventListeners() {
578
- document.addEventListener("click", ev => {
579
- if ( ui.context ) ui.context.close();
580
- });
581
- };
582
- }
372
+ const fileNode = expandToNode `
373
+ export class ContextMenu2 {
374
+ constructor(element, selector, menuItems, {eventName="contextmenu"}={}) {
375
+
376
+ /**
377
+ * The target HTMLElement being selected
378
+ * @type {HTMLElement}
379
+ */
380
+ this.element = element;
381
+
382
+ /**
383
+ * The target CSS selector which activates the menu
384
+ * @type {String}
385
+ */
386
+ this.selector = selector || element.attr("id");
387
+
388
+ /**
389
+ * An interaction event name which activates the menu
390
+ * @type {String}
391
+ */
392
+ this.eventName = eventName;
393
+
394
+ /**
395
+ * The array of menu items being rendered
396
+ * @type {Array}
397
+ */
398
+ this.menuItems = menuItems;
399
+
400
+ /**
401
+ * Track which direction the menu is expanded in
402
+ * @type {Boolean}
403
+ */
404
+ this._expandUp = false;
405
+
406
+ // Bind to the current element
407
+ this.bind();
408
+ }
409
+
410
+ /* -------------------------------------------- */
411
+
412
+ /**
413
+ * A convenience accessor to the context menu HTML object
414
+ * @return {*|jQuery.fn.init|jQuery|HTMLElement}
415
+ */
416
+ get menu() {
417
+ return $("#context-menu2");
418
+ }
419
+
420
+ /* -------------------------------------------- */
421
+
422
+ /**
423
+ * Attach a ContextMenu instance to an HTML selector
424
+ */
425
+ bind() {
426
+ this.element.on(this.eventName, this.selector, event => {
427
+ event.preventDefault();
428
+ event.stopPropagation();
429
+ let parent = $(event.currentTarget);
430
+
431
+ if (this.selector == ".message") return;
432
+
433
+ // Remove existing context UI
434
+ $('.context').removeClass("context");
435
+
436
+ // The menu mounts on <body> (not inside the target), so a fresh right-click can't
437
+ // rely on DOM containment to toggle. Tear down any open menu immediately to avoid
438
+ // duplicates/animation races, then open a fresh one anchored to this target.
439
+ $("#context-menu2").stop(true, true).remove();
440
+ if ( ui.context ) delete ui.context;
441
+
442
+ this.render(parent);
443
+ ui.context = this;
444
+ })
445
+ }
446
+
447
+ /* -------------------------------------------- */
448
+
449
+ /**
450
+ * Animate closing the menu by sliding up and removing from the DOM
451
+ */
452
+ async close() {
453
+ let menu = this.menu;
454
+ await this._animateClose(menu);
455
+ menu.remove();
456
+ $('.context').removeClass("context");
457
+ delete ui.context;
458
+ }
459
+
460
+ /* -------------------------------------------- */
461
+
462
+ async _animateOpen(menu) {
463
+ menu.hide();
464
+ return new Promise(resolve => menu.slideDown(200, resolve));
465
+ }
466
+
467
+ /* -------------------------------------------- */
468
+
469
+ async _animateClose(menu) {
470
+ return new Promise(resolve => menu.slideUp(200, resolve));
471
+ }
472
+
473
+ /* -------------------------------------------- */
474
+
475
+ /**
476
+ * Render the Context Menu by iterating over the menuItems it contains
477
+ * Check the visibility of each menu item, and only render ones which are allowed by the item's logical condition
478
+ * Attach a click handler to each item which is rendered
479
+ * @param target
480
+ */
481
+ render(target) {
482
+ // Always a fresh node -- bind() removed any prior menu, so appending h2/items here
483
+ // can't accumulate onto a reused element.
484
+ let html = $('<nav id="context-menu2" data-mod="1"></nav>');
485
+ let ol = $('<ol class="context-items"></ol>');
486
+ html.append($(\`<h2>\${game.i18n.localize('CONTEXT.ApplyChanges')}</h2>\`));
487
+ html.append(ol);
488
+
489
+ // Determine if user-selected targets are allowed.
490
+ const allowTargeting = game.settings.get('${id}', 'allowTargetDamageApplication');
491
+ let targetType = game.settings.get('${id}', 'userTargetDamageApplicationType');
492
+ if (!allowTargeting && targetType !== 'selected') {
493
+ game.settings.set('${id}', 'userTargetDamageApplicationType', 'selected');
494
+ targetType = 'selected';
495
+ }
496
+
497
+ // Add default target type.
498
+ html[0].dataset.target = targetType;
499
+
500
+ // Build menu items
501
+ for (let item of this.menuItems) {
502
+
503
+ // Determine menu item visibility (display unless false)
504
+ let display = true;
505
+ if ( item.condition !== undefined ) {
506
+ display = ( item.condition instanceof Function ) ? item.condition(target) : item.condition;
507
+ }
508
+ if ( !display ) continue;
509
+
510
+ // Construct and add the menu item
511
+ let name = game.i18n.localize(item.name);
512
+ let li = $(\`<li class="context-item \${item?.id ?? ''}">\${item.icon}\${name}</li>\`);
513
+ // If this is the target buttons option, set one of them to active.
514
+ if (name.includes('data-target="targeted"')) {
515
+ const button = li.find(\`[data-target="\${targetType}"]\`);
516
+ button.addClass('active');
517
+ }
518
+ li.children("i").addClass("fa-fw");
519
+ li.click(e => {
520
+ e.preventDefault();
521
+ e.stopPropagation();
522
+ item.callback(target, e);
523
+ // If this was a target button, prevent closing the context menu.
524
+ if (!item?.preventClose) {
525
+ this.close();
526
+ }
527
+ });
528
+ ol.append(li);
529
+ }
530
+
531
+ // Bail out if there are no children
532
+ if ( ol.children().length === 0 ) return;
533
+
534
+ // Append to target
535
+ this._setPosition(html, target);
536
+
537
+ // Deactivate global tooltip
538
+ game.tooltip.deactivate();
539
+
540
+ // Animate open the menu
541
+ return this._animateOpen(html);
542
+ }
543
+
544
+ /* -------------------------------------------- */
545
+
546
+ /**
547
+ * Set the position of the context menu, taking into consideration whether the menu should expand upward or downward
548
+ * @private
549
+ */
550
+ _setPosition(html, target) {
551
+ const targetRect = target[0].getBoundingClientRect();
552
+
553
+ // Mount on <body> with fixed positioning so the menu escapes the chat log's overflow
554
+ // clipping (#chat-log is overflow-x:hidden / overflow-y:auto) and any transformed sheet
555
+ // ancestor. We then place it explicitly in viewport coordinates.
556
+ html.css("visibility", "hidden");
557
+ document.body.appendChild(html[0]);
558
+ const contextRect = html[0].getBoundingClientRect();
559
+
560
+ // Expand upward only when there isn't room below the target but there is above it.
561
+ const roomBelow = window.innerHeight - targetRect.bottom;
562
+ this._expandUp = (roomBelow < contextRect.height) && (targetRect.top > contextRect.height);
563
+ const top = this._expandUp
564
+ ? targetRect.top - contextRect.height - 2
565
+ : targetRect.bottom + 2;
566
+
567
+ // Align to the target's left edge, clamped to stay within the viewport.
568
+ let left = targetRect.left;
569
+ const maxLeft = window.innerWidth - contextRect.width - 4;
570
+ if (left > maxLeft) left = maxLeft;
571
+ if (left < 4) left = 4;
572
+
573
+ // Display the menu
574
+ html.css({ top: Math.round(top) + "px", left: Math.round(left) + "px" });
575
+ html.addClass(this._expandUp ? "expand-up" : "expand-down");
576
+ html.css("visibility", "");
577
+ target.addClass("context");
578
+ }
579
+
580
+ /* -------------------------------------------- */
581
+
582
+ static eventListeners() {
583
+ document.addEventListener("click", ev => {
584
+ if ( ui.context ) ui.context.close();
585
+ });
586
+ };
587
+ }
583
588
  `.appendNewLineIfNotEmpty();
584
589
  fs.writeFileSync(generatedFilePath, toString(fileNode));
585
590
  }
@@ -589,30 +594,30 @@ function generateDocumentCreateHbs(entry, id, destination) {
589
594
  if (!fs.existsSync(generatedFileDir)) {
590
595
  fs.mkdirSync(generatedFileDir, { recursive: true });
591
596
  }
592
- const fileNode = expandToNode `
593
- <form id="document-create" autocomplete="off">
594
- <header>
595
- <input type="text" class="document-name uninput" name="name" value="{{ name }}" autofocus
596
- placeholder="{{ localize 'Name' }}">
597
- {{#if hasFolders}}
598
- <select class="unselect" name="folder" form="document-create">
599
- {{ selectOptions folders selected=folder labelAttr="name" valueAttr="id"
600
- blank=(localize "DOCUMENT.Folder") }}
601
- </select>
602
- {{/if}}
603
- </header>
604
- <ol class="unlist card">
605
- {{#each types}}
606
- <li data-tooltip="{{ description }}">
607
- <label>
608
- <img src="{{ icon }}" alt="{{ label }}">
609
- <span>{{ label }}</span>
610
- <input type="radio" name="type" value="{{ type }}" required {{#if selected}}checked{{/if}}>
611
- </label>
612
- </li>
613
- {{/each}}
614
- </ol>
615
- </form>
597
+ const fileNode = expandToNode `
598
+ <form id="document-create" autocomplete="off">
599
+ <header>
600
+ <input type="text" class="document-name uninput" name="name" value="{{ name }}" autofocus
601
+ placeholder="{{ localize 'Name' }}">
602
+ {{#if hasFolders}}
603
+ <select class="unselect" name="folder" form="document-create">
604
+ {{ selectOptions folders selected=folder labelAttr="name" valueAttr="id"
605
+ blank=(localize "DOCUMENT.Folder") }}
606
+ </select>
607
+ {{/if}}
608
+ </header>
609
+ <ol class="unlist card">
610
+ {{#each types}}
611
+ <li data-tooltip="{{ description }}">
612
+ <label>
613
+ <img src="{{ icon }}" alt="{{ label }}">
614
+ <span>{{ label }}</span>
615
+ <input type="radio" name="type" value="{{ type }}" required {{#if selected}}checked{{/if}}>
616
+ </label>
617
+ </li>
618
+ {{/each}}
619
+ </ol>
620
+ </form>
616
621
  `.appendNewLineIfNotEmpty();
617
622
  fs.writeFileSync(generatedFilePath, toString(fileNode));
618
623
  }
@@ -622,12 +627,12 @@ function generateCombatant(entry, id, destination) {
622
627
  if (!fs.existsSync(generatedFileDir)) {
623
628
  fs.mkdirSync(generatedFileDir, { recursive: true });
624
629
  }
625
- const fileNode = expandToNode `
626
- export default class ${entry.config.name}Combatant extends Combatant {
627
- _getInitiativeFormula() {
628
- return String(CONFIG.Combat.initiative.formula || game.system.initiative || this.actor.getInitiativeFormula());
629
- }
630
- }
630
+ const fileNode = expandToNode `
631
+ export default class ${entry.config.name}Combatant extends Combatant {
632
+ _getInitiativeFormula() {
633
+ return String(CONFIG.Combat.initiative.formula || game.system.initiative || this.actor.getInitiativeFormula());
634
+ }
635
+ }
631
636
  `.appendNewLineIfNotEmpty();
632
637
  fs.writeFileSync(generatedFilePath, toString(fileNode));
633
638
  }