intelligent-system-design-language 0.3.22 → 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 (91) 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 -1444
  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 -1444
  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 -654
  27. package/out/cli/components/css-generator.js +4 -4
  28. package/out/cli/components/damage-roll-generator.js +160 -160
  29. package/out/cli/components/datamodel-generator.js +257 -257
  30. package/out/cli/components/derived-data-generator.js +923 -923
  31. package/out/cli/components/hotbar-drop-hook-generator.js +82 -82
  32. package/out/cli/components/init-hook-generator.js +495 -495
  33. package/out/cli/components/measured-template-preview.js +221 -221
  34. package/out/cli/components/method-generator.js +548 -548
  35. package/out/cli/components/ready-hook-generator.js +404 -404
  36. package/out/cli/components/token-generator.js +116 -116
  37. package/out/cli/components/vue/base-components/vue-attribute.js +138 -138
  38. package/out/cli/components/vue/base-components/vue-boolean.js +64 -64
  39. package/out/cli/components/vue/base-components/vue-calculator.js +93 -93
  40. package/out/cli/components/vue/base-components/vue-damage-application.js +356 -356
  41. package/out/cli/components/vue/base-components/vue-damage-bonuses.js +165 -165
  42. package/out/cli/components/vue/base-components/vue-damage-resistances.js +196 -196
  43. package/out/cli/components/vue/base-components/vue-damage-track.js +121 -121
  44. package/out/cli/components/vue/base-components/vue-date-time.js +42 -42
  45. package/out/cli/components/vue/base-components/vue-dice.js +98 -98
  46. package/out/cli/components/vue/base-components/vue-die.js +73 -73
  47. package/out/cli/components/vue/base-components/vue-document-choice.js +149 -149
  48. package/out/cli/components/vue/base-components/vue-document-choices.js +179 -179
  49. package/out/cli/components/vue/base-components/vue-document-link.js +60 -60
  50. package/out/cli/components/vue/base-components/vue-extended-choice.js +88 -88
  51. package/out/cli/components/vue/base-components/vue-inventory.js +519 -519
  52. package/out/cli/components/vue/base-components/vue-macro-choice.js +138 -138
  53. package/out/cli/components/vue/base-components/vue-measured-template.js +530 -530
  54. package/out/cli/components/vue/base-components/vue-money.js +483 -483
  55. package/out/cli/components/vue/base-components/vue-number.js +174 -174
  56. package/out/cli/components/vue/base-components/vue-paperdoll.js +43 -43
  57. package/out/cli/components/vue/base-components/vue-parent-property-reference.js +76 -76
  58. package/out/cli/components/vue/base-components/vue-prosemirror.js +18 -18
  59. package/out/cli/components/vue/base-components/vue-resource.js +136 -136
  60. package/out/cli/components/vue/base-components/vue-roll-visualizer.js +285 -285
  61. package/out/cli/components/vue/base-components/vue-self-property-reference.js +62 -62
  62. package/out/cli/components/vue/base-components/vue-string-choice.js +98 -98
  63. package/out/cli/components/vue/base-components/vue-string-choices.js +203 -203
  64. package/out/cli/components/vue/base-components/vue-string.js +60 -60
  65. package/out/cli/components/vue/base-components/vue-text-field.js +53 -53
  66. package/out/cli/components/vue/base-components/vue-tracker.js +431 -431
  67. package/out/cli/components/vue/vue-action-component-generator.js +64 -64
  68. package/out/cli/components/vue/vue-active-effect-sheet-generator.js +856 -856
  69. package/out/cli/components/vue/vue-datatable-sheet-class-generator.js +292 -292
  70. package/out/cli/components/vue/vue-datatable2-component-generator.js +824 -824
  71. package/out/cli/components/vue/vue-document-creation-app.js +121 -121
  72. package/out/cli/components/vue/vue-document-creation-sheet.js +94 -94
  73. package/out/cli/components/vue/vue-generator.js +40 -40
  74. package/out/cli/components/vue/vue-mixin.js +296 -296
  75. package/out/cli/components/vue/vue-pinned-datatable-component-generator.js +260 -260
  76. package/out/cli/components/vue/vue-prompt-generator.js +80 -80
  77. package/out/cli/components/vue/vue-prompt-sheet-class-generator.js +317 -317
  78. package/out/cli/components/vue/vue-sheet-application-generator.js +1171 -1171
  79. package/out/cli/components/vue/vue-sheet-class-generator.js +510 -510
  80. package/out/cli/generator.js +438 -438
  81. package/out/extension/github/githubManager.js +35 -35
  82. package/out/extension/github/githubQuickActions.js +120 -120
  83. package/out/extension/github/system-workflow.yml +47 -47
  84. package/out/extension/main.cjs.map +1 -1
  85. package/out/extension/package.json +419 -419
  86. package/out/language/generated/grammar.js +14240 -14240
  87. package/out/language/main.cjs.map +1 -1
  88. package/out/package.json +419 -419
  89. package/out/progressbar.min.js +6 -6
  90. package/out/styles.scss +762 -762
  91. 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,268 +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
-
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
- }
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
+ }
588
588
  `.appendNewLineIfNotEmpty();
589
589
  fs.writeFileSync(generatedFilePath, toString(fileNode));
590
590
  }
@@ -594,30 +594,30 @@ function generateDocumentCreateHbs(entry, id, destination) {
594
594
  if (!fs.existsSync(generatedFileDir)) {
595
595
  fs.mkdirSync(generatedFileDir, { recursive: true });
596
596
  }
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>
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>
621
621
  `.appendNewLineIfNotEmpty();
622
622
  fs.writeFileSync(generatedFilePath, toString(fileNode));
623
623
  }
@@ -627,12 +627,12 @@ function generateCombatant(entry, id, destination) {
627
627
  if (!fs.existsSync(generatedFileDir)) {
628
628
  fs.mkdirSync(generatedFileDir, { recursive: true });
629
629
  }
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
- }
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
+ }
636
636
  `.appendNewLineIfNotEmpty();
637
637
  fs.writeFileSync(generatedFilePath, toString(fileNode));
638
638
  }