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.
- package/.claude/agents/langium-language-designer.md +38 -38
- package/.claude/agents/typescript-vscode-expert.md +29 -29
- package/.claude/agents/ui-ux-designer.md +36 -36
- package/.claude/settings.local.json +33 -33
- package/.idea/inspectionProfiles/Project_Default.xml +6 -6
- package/.idea/isdl.iml +13 -13
- package/.idea/modules.xml +8 -8
- package/.idea/vcs.xml +6 -6
- package/.idea/watcherTasks.xml +3 -3
- package/.vscodeignore +18 -18
- package/LICENSE +673 -673
- package/README.md +86 -86
- package/bin/cli.js +4 -4
- package/bin/lsp.js +8 -8
- package/out/_backgrounds.scss +91 -91
- package/out/_handlebars.scss +497 -497
- package/out/_isdlStyles.scss +1444 -1381
- package/out/_vuetifyOverrides.scss +425 -425
- package/out/_vuetifyStyles.scss +31957 -31957
- package/out/cli/components/_backgrounds.scss +91 -91
- package/out/cli/components/_handlebars.scss +497 -497
- package/out/cli/components/_isdlStyles.scss +1444 -1381
- package/out/cli/components/_vuetifyOverrides.scss +425 -425
- package/out/cli/components/_vuetifyStyles.scss +31957 -31957
- package/out/cli/components/active-effect-sheet-generator.js +453 -453
- package/out/cli/components/chat-card-generator.js +654 -651
- package/out/cli/components/chat-card-generator.js.map +1 -1
- package/out/cli/components/css-generator.js +4 -4
- package/out/cli/components/damage-roll-generator.js +160 -160
- package/out/cli/components/datamodel-generator.js +264 -257
- package/out/cli/components/datamodel-generator.js.map +1 -1
- package/out/cli/components/derived-data-generator.js +923 -923
- package/out/cli/components/hotbar-drop-hook-generator.js +82 -82
- package/out/cli/components/init-hook-generator.js +495 -495
- package/out/cli/components/language-generator.js +1 -1
- package/out/cli/components/language-generator.js.map +1 -1
- package/out/cli/components/measured-template-preview.js +221 -221
- package/out/cli/components/method-generator.js +979 -887
- package/out/cli/components/method-generator.js.map +1 -1
- package/out/cli/components/ready-hook-generator.js +404 -404
- package/out/cli/components/token-generator.js +116 -116
- package/out/cli/components/vue/base-components/vue-attribute.js +138 -138
- package/out/cli/components/vue/base-components/vue-boolean.js +64 -64
- package/out/cli/components/vue/base-components/vue-calculator.js +93 -93
- package/out/cli/components/vue/base-components/vue-damage-application.js +356 -356
- package/out/cli/components/vue/base-components/vue-damage-bonuses.js +165 -165
- package/out/cli/components/vue/base-components/vue-damage-resistances.js +196 -196
- package/out/cli/components/vue/base-components/vue-damage-track.js +121 -121
- package/out/cli/components/vue/base-components/vue-date-time.js +42 -42
- package/out/cli/components/vue/base-components/vue-dice.js +98 -98
- package/out/cli/components/vue/base-components/vue-die.js +73 -73
- package/out/cli/components/vue/base-components/vue-document-choice.js +149 -149
- package/out/cli/components/vue/base-components/vue-document-choices.js +179 -179
- package/out/cli/components/vue/base-components/vue-document-link.js +60 -60
- package/out/cli/components/vue/base-components/vue-extended-choice.js +88 -88
- package/out/cli/components/vue/base-components/vue-inventory.js +519 -519
- package/out/cli/components/vue/base-components/vue-macro-choice.js +138 -138
- package/out/cli/components/vue/base-components/vue-measured-template.js +530 -530
- package/out/cli/components/vue/base-components/vue-money.js +483 -483
- package/out/cli/components/vue/base-components/vue-number.js +174 -174
- package/out/cli/components/vue/base-components/vue-paperdoll.js +43 -43
- package/out/cli/components/vue/base-components/vue-parent-property-reference.js +76 -76
- package/out/cli/components/vue/base-components/vue-prosemirror.js +18 -18
- package/out/cli/components/vue/base-components/vue-resource.js +136 -136
- package/out/cli/components/vue/base-components/vue-roll-visualizer.js +286 -109
- package/out/cli/components/vue/base-components/vue-roll-visualizer.js.map +1 -1
- package/out/cli/components/vue/base-components/vue-self-property-reference.js +62 -62
- package/out/cli/components/vue/base-components/vue-string-choice.js +98 -98
- package/out/cli/components/vue/base-components/vue-string-choices.js +203 -203
- package/out/cli/components/vue/base-components/vue-string.js +60 -60
- package/out/cli/components/vue/base-components/vue-text-field.js +53 -53
- package/out/cli/components/vue/base-components/vue-tracker.js +431 -431
- package/out/cli/components/vue/vue-action-component-generator.js +64 -64
- package/out/cli/components/vue/vue-active-effect-sheet-generator.js +856 -856
- package/out/cli/components/vue/vue-datatable-sheet-class-generator.js +292 -292
- package/out/cli/components/vue/vue-datatable2-component-generator.js +824 -824
- package/out/cli/components/vue/vue-document-creation-app.js +121 -121
- package/out/cli/components/vue/vue-document-creation-sheet.js +94 -94
- package/out/cli/components/vue/vue-generator.js +40 -40
- package/out/cli/components/vue/vue-mixin.js +296 -296
- package/out/cli/components/vue/vue-pinned-datatable-component-generator.js +260 -260
- package/out/cli/components/vue/vue-prompt-generator.js +91 -76
- package/out/cli/components/vue/vue-prompt-generator.js.map +1 -1
- package/out/cli/components/vue/vue-prompt-sheet-class-generator.js +317 -317
- package/out/cli/components/vue/vue-sheet-application-generator.js +1177 -1167
- package/out/cli/components/vue/vue-sheet-application-generator.js.map +1 -1
- package/out/cli/components/vue/vue-sheet-class-generator.js +510 -510
- package/out/cli/generator.js +438 -433
- package/out/cli/generator.js.map +1 -1
- package/out/extension/github/githubAuthProvider.js +71 -29
- package/out/extension/github/githubAuthProvider.js.map +1 -1
- package/out/extension/github/githubGistManager.js +4 -3
- package/out/extension/github/githubGistManager.js.map +1 -1
- package/out/extension/github/githubManager.js +40 -38
- package/out/extension/github/githubManager.js.map +1 -1
- package/out/extension/github/githubQuickActions.js +120 -120
- package/out/extension/github/system-workflow.yml +47 -47
- package/out/extension/main.cjs +909 -532
- package/out/extension/main.cjs.map +3 -3
- package/out/extension/package.json +419 -419
- package/out/language/generated/ast.js +51 -2
- package/out/language/generated/ast.js.map +1 -1
- package/out/language/generated/grammar.js +14240 -13991
- package/out/language/generated/grammar.js.map +1 -1
- package/out/language/intelligent-system-design-language-validator.js +32 -2
- package/out/language/intelligent-system-design-language-validator.js.map +1 -1
- package/out/language/isdl-scope-provider.js +14 -1
- package/out/language/isdl-scope-provider.js.map +1 -1
- package/out/language/main.cjs +913 -569
- package/out/language/main.cjs.map +3 -3
- package/out/package.json +419 -419
- package/out/progressbar.min.js +6 -6
- package/out/styles.scss +762 -747
- package/out/test/validating/diagnostics.test.js +40 -0
- package/out/test/validating/diagnostics.test.js.map +1 -1
- package/package.json +419 -419
|
@@ -15,410 +15,410 @@ export function generateReadyHookMjs(entry, id, destination) {
|
|
|
15
15
|
if (!fs.existsSync(generatedFileDir)) {
|
|
16
16
|
fs.mkdirSync(generatedFileDir, { recursive: true });
|
|
17
17
|
}
|
|
18
|
-
const fileNode = expandToNode `
|
|
19
|
-
export async function ready() {
|
|
20
|
-
console.log('${id} | Ready');
|
|
21
|
-
|
|
22
|
-
registerSockets();
|
|
23
|
-
//moveVuetifyStyles();
|
|
24
|
-
reopenLastState();
|
|
25
|
-
indexPacks();
|
|
26
|
-
await createKeywordJournals();
|
|
27
|
-
|
|
28
|
-
function getTargetOrNothing() {
|
|
29
|
-
if (game.user.targets.size > 0) {
|
|
30
|
-
const firstTarget = game.user.targets.first();
|
|
31
|
-
return firstTarget.actor;
|
|
32
|
-
}
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
// Attach to game.user
|
|
36
|
-
game.user.getTargetOrNothing = getTargetOrNothing;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/* -------------------------------------------- */
|
|
40
|
-
|
|
41
|
-
function registerSockets() {
|
|
42
|
-
game.socket.on("system.${id}", (data) => {
|
|
43
|
-
console.log("Socket Data", data);
|
|
44
|
-
|
|
45
|
-
if (data.type === "prompt") {
|
|
46
|
-
_handlePrompt(data);
|
|
47
|
-
}
|
|
48
|
-
else if (data.type === "combat") {
|
|
49
|
-
_handleCombat(data);
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/* -------------------------------------------- */
|
|
55
|
-
|
|
56
|
-
async function _handlePrompt(message) {
|
|
57
|
-
// Respond to the requesting client exactly once with the harvested input (or {}).
|
|
58
|
-
let responded = false;
|
|
59
|
-
const respond = (data) => {
|
|
60
|
-
if (responded) return;
|
|
61
|
-
responded = true;
|
|
62
|
-
game.socket.emit("system.${id}", {
|
|
63
|
-
type: "promptResponse",
|
|
64
|
-
uuid: message.uuid,
|
|
65
|
-
data: data ?? {}
|
|
66
|
-
}, { recipients: [message.userId] });
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
// Load the prompt's target document. The receiving user may not own it (e.g. another
|
|
70
|
-
// player or the GM is being prompted), but the Vue prompt app reads it via toObject()
|
|
71
|
-
// and suppresses writes, so no ownership is required.
|
|
72
|
-
const doc = await fromUuid(message.documentUuid);
|
|
73
|
-
const PromptApp = game.system.prompts[message.promptKey];
|
|
74
|
-
if (!doc || !PromptApp) {
|
|
75
|
-
console.warn("Could not open prompt for", message.documentUuid, message.promptKey);
|
|
76
|
-
respond({});
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const options = { promptResolve: respond };
|
|
81
|
-
// Honor any size/location the requester specified.
|
|
82
|
-
const position = {};
|
|
83
|
-
if (message.width) position.width = message.width;
|
|
84
|
-
if (message.height) position.height = message.height;
|
|
85
|
-
if (message.left != null) position.left = message.left;
|
|
86
|
-
if (message.top != null) position.top = message.top;
|
|
87
|
-
if (Object.keys(position).length > 0) options.position = position;
|
|
88
|
-
|
|
89
|
-
const app = new PromptApp(doc, options);
|
|
90
|
-
app.render(true);
|
|
91
|
-
|
|
92
|
-
// Time limit: close the app, which resolves {} through promptResolve (= respond).
|
|
93
|
-
if (message.timeLimit && message.timeLimit > 0) {
|
|
94
|
-
setTimeout(() => {
|
|
95
|
-
if (responded) return;
|
|
96
|
-
console.warn("Prompt timed out:", message.uuid);
|
|
97
|
-
app.close();
|
|
98
|
-
}, message.timeLimit);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/* -------------------------------------------- */
|
|
103
|
-
|
|
104
|
-
async function _handleCombat(message) {
|
|
105
|
-
switch(message.method) {
|
|
106
|
-
case "nextTurn": game.combat.nextTurn(); break;
|
|
107
|
-
case "endCombat": game.combat.endCombat(); break;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/* -------------------------------------------- */
|
|
112
|
-
|
|
113
|
-
function moveVuetifyStyles() {
|
|
114
|
-
|
|
115
|
-
const observer = new MutationObserver((mutationsList) => {
|
|
116
|
-
for (const mutation of mutationsList) {
|
|
117
|
-
if (mutation.type === "childList") {
|
|
118
|
-
const themeStylesheet = document.getElementById("vuetify-theme-stylesheet");
|
|
119
|
-
if (themeStylesheet) {
|
|
120
|
-
console.log("Vuetify theme stylesheet loaded:", themeStylesheet);
|
|
121
|
-
|
|
122
|
-
// Create a new style node
|
|
123
|
-
const vuetifyThemeOverrides = document.createElement("style");
|
|
124
|
-
vuetifyThemeOverrides.id = "vuetify-theme-overrides";
|
|
125
|
-
vuetifyThemeOverrides.innerHTML = \`
|
|
126
|
-
.v-theme--light {
|
|
127
|
-
--v-disabled-opacity: 0.7;
|
|
128
|
-
}
|
|
129
|
-
\`;
|
|
130
|
-
|
|
131
|
-
document.head.insertAdjacentElement('beforeEnd', vuetifyThemeOverrides);
|
|
132
|
-
|
|
133
|
-
// Perform any modifications or actions here
|
|
134
|
-
observer.disconnect(); // Stop observing once found
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
// Observe the <head> for new styles being added
|
|
141
|
-
observer.observe(document.head, { childList: true, subtree: true });
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/* -------------------------------------------- */
|
|
145
|
-
|
|
146
|
-
function reopenLastState() {
|
|
147
|
-
const lastState = game.settings.get("${id}", "hotReloadLastState");
|
|
148
|
-
if (lastState.openWindows.length > 0) {
|
|
149
|
-
for (const window of lastState.openWindows) {
|
|
150
|
-
const document = fromUuidSync(window.uuid);
|
|
151
|
-
const app = document.sheet;
|
|
152
|
-
if (app) {
|
|
153
|
-
try {
|
|
154
|
-
app.render(true).setPosition(window.position);
|
|
155
|
-
}
|
|
156
|
-
catch (e) {}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
game.settings.set("${id}", "hotReloadLastState", { openWindows: [] });
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/* -------------------------------------------- */
|
|
164
|
-
|
|
165
|
-
function indexPacks() {
|
|
166
|
-
for (const pack of game.packs) {
|
|
167
|
-
pack.getIndex({ fields: ['system.description', 'system'] });
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/* -------------------------------------------- */
|
|
172
|
-
|
|
173
|
-
async function createKeywordJournals() {
|
|
174
|
-
if (!game.user.isGM || (!game.system.keywords && !game.system.statusEffects)) return;
|
|
175
|
-
if (!game.settings.get("${id}", "createSystemJournal")) return;
|
|
176
|
-
|
|
177
|
-
// Create System folder if it doesn't exist
|
|
178
|
-
let keywordsFolder = game.folders.find(f => f.name === game.i18n.localize("JOURNAL.System") && f.type === "JournalEntry");
|
|
179
|
-
if (!keywordsFolder) {
|
|
180
|
-
keywordsFolder = await Folder.create({
|
|
181
|
-
name: game.i18n.localize("JOURNAL.System"),
|
|
182
|
-
type: "JournalEntry",
|
|
183
|
-
color: "#42a5f5",
|
|
184
|
-
parent: null,
|
|
185
|
-
sort: 0
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Find or create the system documentation journal
|
|
190
|
-
let keywordsJournal = game.journal.find(j => j.getFlag('${id}', 'systemJournal') === true);
|
|
191
|
-
|
|
192
|
-
if (!keywordsJournal) {
|
|
193
|
-
// Create the main system documentation journal
|
|
194
|
-
const journalData = {
|
|
195
|
-
name: "${systemLabel}",
|
|
196
|
-
folder: keywordsFolder.id,
|
|
197
|
-
flags: {
|
|
198
|
-
core: {
|
|
199
|
-
keywordsJournal: true // Legacy compatibility
|
|
200
|
-
},
|
|
201
|
-
'${id}': {
|
|
202
|
-
systemJournal: true,
|
|
203
|
-
version: game.system.version
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
keywordsJournal = await JournalEntry.create(journalData);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Separate keywords by type
|
|
212
|
-
const regularKeywords = {};
|
|
213
|
-
const damageTypes = {};
|
|
214
|
-
|
|
215
|
-
if (game.system.keywords) {
|
|
216
|
-
for (const [keywordKey, keywordData] of Object.entries(game.system.keywords)) {
|
|
217
|
-
if (keywordData.type === 'damage-type') {
|
|
218
|
-
damageTypes[keywordKey] = keywordData;
|
|
219
|
-
} else {
|
|
220
|
-
regularKeywords[keywordKey] = keywordData;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Use pre-collected status effects data with conditions
|
|
226
|
-
const statusEffects = ${JSON.stringify(statusEffectsData, null, 12).split('\n').join('\n ')};
|
|
227
|
-
|
|
228
|
-
// Check and create/update Keywords page
|
|
229
|
-
let keywordsPage = keywordsJournal.pages.find(p => p.getFlag('core', 'pageType') === 'keywords');
|
|
230
|
-
if (Object.keys(regularKeywords).length > 0) {
|
|
231
|
-
const keywordsContent = generateKeywordsPageContent(regularKeywords);
|
|
232
|
-
|
|
233
|
-
if (!keywordsPage) {
|
|
234
|
-
// Create new page
|
|
235
|
-
const keywordsPageData = {
|
|
236
|
-
name: game.i18n.localize("JOURNAL.Keywords"),
|
|
237
|
-
type: "text",
|
|
238
|
-
title: {
|
|
239
|
-
show: true,
|
|
240
|
-
level: 1
|
|
241
|
-
},
|
|
242
|
-
text: {
|
|
243
|
-
content: keywordsContent,
|
|
244
|
-
format: 1 // HTML format
|
|
245
|
-
},
|
|
246
|
-
flags: {
|
|
247
|
-
core: {
|
|
248
|
-
pageType: 'keywords'
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
};
|
|
252
|
-
|
|
253
|
-
await keywordsJournal.createEmbeddedDocuments("JournalEntryPage", [keywordsPageData]);
|
|
254
|
-
} else {
|
|
255
|
-
// Update existing page content
|
|
256
|
-
await keywordsPage.update({
|
|
257
|
-
"text.content": keywordsContent
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// Check and create/update Damage Types page
|
|
263
|
-
let damageTypesPage = keywordsJournal.pages.find(p => p.getFlag('core', 'pageType') === 'damage-types');
|
|
264
|
-
if (Object.keys(damageTypes).length > 0) {
|
|
265
|
-
const damageTypesContent = generateDamageTypesPageContent(damageTypes);
|
|
266
|
-
|
|
267
|
-
if (!damageTypesPage) {
|
|
268
|
-
// Create new page
|
|
269
|
-
const damageTypesPageData = {
|
|
270
|
-
name: game.i18n.localize("JOURNAL.DamageTypes"),
|
|
271
|
-
type: "text",
|
|
272
|
-
title: {
|
|
273
|
-
show: true,
|
|
274
|
-
level: 1
|
|
275
|
-
},
|
|
276
|
-
text: {
|
|
277
|
-
content: damageTypesContent,
|
|
278
|
-
format: 1 // HTML format
|
|
279
|
-
},
|
|
280
|
-
flags: {
|
|
281
|
-
core: {
|
|
282
|
-
pageType: 'damage-types'
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
await keywordsJournal.createEmbeddedDocuments("JournalEntryPage", [damageTypesPageData]);
|
|
288
|
-
} else {
|
|
289
|
-
// Update existing page content
|
|
290
|
-
await damageTypesPage.update({
|
|
291
|
-
"text.content": damageTypesContent
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// Check and create/update Status Effects page
|
|
297
|
-
let statusEffectsPage = keywordsJournal.pages.find(p => p.getFlag('core', 'pageType') === 'status-effects');
|
|
298
|
-
if (Object.keys(statusEffects).length > 0) {
|
|
299
|
-
const statusEffectsContent = generateStatusEffectsPageContent(statusEffects);
|
|
300
|
-
|
|
301
|
-
if (!statusEffectsPage) {
|
|
302
|
-
// Create new page
|
|
303
|
-
const statusEffectsPageData = {
|
|
304
|
-
name: game.i18n.localize("JOURNAL.StatusEffects"),
|
|
305
|
-
type: "text",
|
|
306
|
-
title: {
|
|
307
|
-
show: true,
|
|
308
|
-
level: 1
|
|
309
|
-
},
|
|
310
|
-
text: {
|
|
311
|
-
content: statusEffectsContent,
|
|
312
|
-
format: 1 // HTML format
|
|
313
|
-
},
|
|
314
|
-
flags: {
|
|
315
|
-
core: {
|
|
316
|
-
pageType: 'status-effects'
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
};
|
|
320
|
-
|
|
321
|
-
await keywordsJournal.createEmbeddedDocuments("JournalEntryPage", [statusEffectsPageData]);
|
|
322
|
-
} else {
|
|
323
|
-
// Update existing page content
|
|
324
|
-
await statusEffectsPage.update({
|
|
325
|
-
"text.content": statusEffectsContent
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
function generateKeywordsPageContent(keywords) {
|
|
332
|
-
let content = \`<div class="isdl-journal-section">
|
|
333
|
-
<div class="isdl-journal-info-box">
|
|
334
|
-
<div class="isdl-journal-info-title">\${game.i18n.localize("JOURNAL.KeywordsUsageTitle")}</div>
|
|
335
|
-
<ul class="isdl-journal-info-list">
|
|
336
|
-
<li>\${game.i18n.localize("JOURNAL.KeywordsUsageList.GameMechanics")}</li>
|
|
337
|
-
<li>\${game.i18n.localize("JOURNAL.KeywordsUsageList.References")}</li>
|
|
338
|
-
<li>\${game.i18n.localize("JOURNAL.KeywordsUsageList.Documentation")}</li>
|
|
339
|
-
</ul>
|
|
340
|
-
</div>
|
|
341
|
-
</div>\`;
|
|
342
|
-
|
|
343
|
-
for (const [keywordKey, keyword] of Object.entries(keywords)) {
|
|
344
|
-
content += \`<div class="isdl-keyword-entry" id="\${keywordKey}" style="border-left-color: \${keyword.color};">
|
|
345
|
-
<div class="isdl-journal-header">
|
|
346
|
-
\${keyword.icon ? \`<i class="\${keyword.icon} isdl-journal-icon" style="color: \${keyword.color};"></i>\` : ''}
|
|
347
|
-
<h3 class="isdl-journal-title">\${keyword.name}</h3>
|
|
348
|
-
<span class="isdl-journal-badge" style="background: \${keyword.color}20;">\${game.i18n.localize("JOURNAL.KeywordBadge")}</span>
|
|
349
|
-
</div>
|
|
350
|
-
\${keyword.description ? \`<div class="isdl-journal-description"><p>\${keyword.description}</p></div>\` : ''}
|
|
351
|
-
</div>\`;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
return content;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
function generateDamageTypesPageContent(damageTypes) {
|
|
358
|
-
let content = \`<div class="isdl-journal-section">
|
|
359
|
-
<div class="isdl-journal-info-box">
|
|
360
|
-
<div class="isdl-journal-info-title">\${game.i18n.localize("JOURNAL.DamageTypeEffectsTitle")}</div>
|
|
361
|
-
<ul class="isdl-journal-info-list">
|
|
362
|
-
<li>\${game.i18n.localize("JOURNAL.DamageTypeEffectsList.Usage")}</li>
|
|
363
|
-
<li>\${game.i18n.localize("JOURNAL.DamageTypeEffectsList.Resistances")}</li>
|
|
364
|
-
<li>\${game.i18n.localize("JOURNAL.DamageTypeEffectsList.ChoiceFields")}</li>
|
|
365
|
-
</ul>
|
|
366
|
-
</div>
|
|
367
|
-
</div>\`;
|
|
368
|
-
|
|
369
|
-
for (const [keywordKey, keyword] of Object.entries(damageTypes)) {
|
|
370
|
-
content += \`<div class="isdl-damage-type-entry" id="\${keywordKey}" style="border-left-color: \${keyword.color};">
|
|
371
|
-
<div class="isdl-journal-header">
|
|
372
|
-
\${keyword.icon ? \`<i class="\${keyword.icon} isdl-journal-icon" style="color: \${keyword.color};"></i>\` : ''}
|
|
373
|
-
<h3 class="isdl-journal-title" style="color: \${keyword.color};">\${keyword.name}</h3>
|
|
374
|
-
<span class="isdl-journal-badge" style="background: \${keyword.color}20;">\${game.i18n.localize("JOURNAL.DamageTypeBadge")}</span>
|
|
375
|
-
</div>
|
|
376
|
-
\${keyword.description ? \`<div class="isdl-journal-description"><p>\${keyword.description}</p></div>\` : ''}
|
|
377
|
-
</div>\`;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
return content;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
function localizeConditionText(conditionText) {
|
|
384
|
-
if (!conditionText) return conditionText;
|
|
385
|
-
|
|
386
|
-
// Replace localization key patterns {{KEY}} with actual translations
|
|
387
|
-
return conditionText.replace(/\{\{([^}]+)\}\}/g, (match, key) => {
|
|
388
|
-
return game.i18n.localize(key);
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
function generateStatusEffectsPageContent(statusEffects) {
|
|
393
|
-
let content = \`<div class="isdl-journal-section">
|
|
394
|
-
<div class="isdl-journal-info-box">
|
|
395
|
-
<div class="isdl-journal-info-title">\${game.i18n.localize("JOURNAL.StatusEffectUsageTitle")}</div>
|
|
396
|
-
<ul class="isdl-journal-info-list">
|
|
397
|
-
<li>\${game.i18n.localize("JOURNAL.StatusEffectUsageList.TokenApplication")}</li>
|
|
398
|
-
<li>\${game.i18n.localize("JOURNAL.StatusEffectUsageList.MenuAppearance")}</li>
|
|
399
|
-
<li>\${game.i18n.localize("JOURNAL.StatusEffectUsageList.VisualIndicators")}</li>
|
|
400
|
-
</ul>
|
|
401
|
-
</div>
|
|
402
|
-
</div>\`;
|
|
403
|
-
for (const [statusId, statusEffect] of Object.entries(statusEffects)) {
|
|
404
|
-
const isDeathEffect = statusEffect.isDeath || statusId.toLowerCase().includes('dead');
|
|
405
|
-
const effectColor = isDeathEffect ? '#dc2626' : '#4f46e5';
|
|
406
|
-
const effectBadge = isDeathEffect ? game.i18n.localize("JOURNAL.DeathEffectBadge") : game.i18n.localize("JOURNAL.StatusEffectBadge");
|
|
407
|
-
|
|
408
|
-
content += \`<div class="isdl-status-effect-entry" id="\${statusId}" style="border-left-color: \${effectColor};">
|
|
409
|
-
<div class="isdl-journal-header">
|
|
410
|
-
\${statusEffect.img ? \`<img src="\${statusEffect.img}" alt="\${statusEffect.name}" class="isdl-status-effect-image" />\` : ''}
|
|
411
|
-
<h3 class="isdl-journal-title" style="color: \${effectColor};">\${statusEffect.name}</h3>
|
|
412
|
-
<span class="isdl-journal-badge" style="background: \${effectColor}20; color: \${effectColor};">\${effectBadge}</span>
|
|
413
|
-
</div>
|
|
414
|
-
\${statusEffect.condition ? \`<div class="isdl-journal-condition">
|
|
415
|
-
<p><span class="isdl-journal-condition-label">\${game.i18n.localize("JOURNAL.AppliedWhen")}</span> \${localizeConditionText(statusEffect.condition)}</p>
|
|
416
|
-
</div>\` : ''}
|
|
417
|
-
</div>\`;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
return content;
|
|
421
|
-
}
|
|
18
|
+
const fileNode = expandToNode `
|
|
19
|
+
export async function ready() {
|
|
20
|
+
console.log('${id} | Ready');
|
|
21
|
+
|
|
22
|
+
registerSockets();
|
|
23
|
+
//moveVuetifyStyles();
|
|
24
|
+
reopenLastState();
|
|
25
|
+
indexPacks();
|
|
26
|
+
await createKeywordJournals();
|
|
27
|
+
|
|
28
|
+
function getTargetOrNothing() {
|
|
29
|
+
if (game.user.targets.size > 0) {
|
|
30
|
+
const firstTarget = game.user.targets.first();
|
|
31
|
+
return firstTarget.actor;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
// Attach to game.user
|
|
36
|
+
game.user.getTargetOrNothing = getTargetOrNothing;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* -------------------------------------------- */
|
|
40
|
+
|
|
41
|
+
function registerSockets() {
|
|
42
|
+
game.socket.on("system.${id}", (data) => {
|
|
43
|
+
console.log("Socket Data", data);
|
|
44
|
+
|
|
45
|
+
if (data.type === "prompt") {
|
|
46
|
+
_handlePrompt(data);
|
|
47
|
+
}
|
|
48
|
+
else if (data.type === "combat") {
|
|
49
|
+
_handleCombat(data);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* -------------------------------------------- */
|
|
55
|
+
|
|
56
|
+
async function _handlePrompt(message) {
|
|
57
|
+
// Respond to the requesting client exactly once with the harvested input (or {}).
|
|
58
|
+
let responded = false;
|
|
59
|
+
const respond = (data) => {
|
|
60
|
+
if (responded) return;
|
|
61
|
+
responded = true;
|
|
62
|
+
game.socket.emit("system.${id}", {
|
|
63
|
+
type: "promptResponse",
|
|
64
|
+
uuid: message.uuid,
|
|
65
|
+
data: data ?? {}
|
|
66
|
+
}, { recipients: [message.userId] });
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Load the prompt's target document. The receiving user may not own it (e.g. another
|
|
70
|
+
// player or the GM is being prompted), but the Vue prompt app reads it via toObject()
|
|
71
|
+
// and suppresses writes, so no ownership is required.
|
|
72
|
+
const doc = await fromUuid(message.documentUuid);
|
|
73
|
+
const PromptApp = game.system.prompts[message.promptKey];
|
|
74
|
+
if (!doc || !PromptApp) {
|
|
75
|
+
console.warn("Could not open prompt for", message.documentUuid, message.promptKey);
|
|
76
|
+
respond({});
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const options = { promptResolve: respond };
|
|
81
|
+
// Honor any size/location the requester specified.
|
|
82
|
+
const position = {};
|
|
83
|
+
if (message.width) position.width = message.width;
|
|
84
|
+
if (message.height) position.height = message.height;
|
|
85
|
+
if (message.left != null) position.left = message.left;
|
|
86
|
+
if (message.top != null) position.top = message.top;
|
|
87
|
+
if (Object.keys(position).length > 0) options.position = position;
|
|
88
|
+
|
|
89
|
+
const app = new PromptApp(doc, options);
|
|
90
|
+
app.render(true);
|
|
91
|
+
|
|
92
|
+
// Time limit: close the app, which resolves {} through promptResolve (= respond).
|
|
93
|
+
if (message.timeLimit && message.timeLimit > 0) {
|
|
94
|
+
setTimeout(() => {
|
|
95
|
+
if (responded) return;
|
|
96
|
+
console.warn("Prompt timed out:", message.uuid);
|
|
97
|
+
app.close();
|
|
98
|
+
}, message.timeLimit);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* -------------------------------------------- */
|
|
103
|
+
|
|
104
|
+
async function _handleCombat(message) {
|
|
105
|
+
switch(message.method) {
|
|
106
|
+
case "nextTurn": game.combat.nextTurn(); break;
|
|
107
|
+
case "endCombat": game.combat.endCombat(); break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* -------------------------------------------- */
|
|
112
|
+
|
|
113
|
+
function moveVuetifyStyles() {
|
|
114
|
+
|
|
115
|
+
const observer = new MutationObserver((mutationsList) => {
|
|
116
|
+
for (const mutation of mutationsList) {
|
|
117
|
+
if (mutation.type === "childList") {
|
|
118
|
+
const themeStylesheet = document.getElementById("vuetify-theme-stylesheet");
|
|
119
|
+
if (themeStylesheet) {
|
|
120
|
+
console.log("Vuetify theme stylesheet loaded:", themeStylesheet);
|
|
121
|
+
|
|
122
|
+
// Create a new style node
|
|
123
|
+
const vuetifyThemeOverrides = document.createElement("style");
|
|
124
|
+
vuetifyThemeOverrides.id = "vuetify-theme-overrides";
|
|
125
|
+
vuetifyThemeOverrides.innerHTML = \`
|
|
126
|
+
.v-theme--light {
|
|
127
|
+
--v-disabled-opacity: 0.7;
|
|
128
|
+
}
|
|
129
|
+
\`;
|
|
130
|
+
|
|
131
|
+
document.head.insertAdjacentElement('beforeEnd', vuetifyThemeOverrides);
|
|
132
|
+
|
|
133
|
+
// Perform any modifications or actions here
|
|
134
|
+
observer.disconnect(); // Stop observing once found
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Observe the <head> for new styles being added
|
|
141
|
+
observer.observe(document.head, { childList: true, subtree: true });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* -------------------------------------------- */
|
|
145
|
+
|
|
146
|
+
function reopenLastState() {
|
|
147
|
+
const lastState = game.settings.get("${id}", "hotReloadLastState");
|
|
148
|
+
if (lastState.openWindows.length > 0) {
|
|
149
|
+
for (const window of lastState.openWindows) {
|
|
150
|
+
const document = fromUuidSync(window.uuid);
|
|
151
|
+
const app = document.sheet;
|
|
152
|
+
if (app) {
|
|
153
|
+
try {
|
|
154
|
+
app.render(true).setPosition(window.position);
|
|
155
|
+
}
|
|
156
|
+
catch (e) {}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
game.settings.set("${id}", "hotReloadLastState", { openWindows: [] });
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* -------------------------------------------- */
|
|
164
|
+
|
|
165
|
+
function indexPacks() {
|
|
166
|
+
for (const pack of game.packs) {
|
|
167
|
+
pack.getIndex({ fields: ['system.description', 'system'] });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* -------------------------------------------- */
|
|
172
|
+
|
|
173
|
+
async function createKeywordJournals() {
|
|
174
|
+
if (!game.user.isGM || (!game.system.keywords && !game.system.statusEffects)) return;
|
|
175
|
+
if (!game.settings.get("${id}", "createSystemJournal")) return;
|
|
176
|
+
|
|
177
|
+
// Create System folder if it doesn't exist
|
|
178
|
+
let keywordsFolder = game.folders.find(f => f.name === game.i18n.localize("JOURNAL.System") && f.type === "JournalEntry");
|
|
179
|
+
if (!keywordsFolder) {
|
|
180
|
+
keywordsFolder = await Folder.create({
|
|
181
|
+
name: game.i18n.localize("JOURNAL.System"),
|
|
182
|
+
type: "JournalEntry",
|
|
183
|
+
color: "#42a5f5",
|
|
184
|
+
parent: null,
|
|
185
|
+
sort: 0
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Find or create the system documentation journal
|
|
190
|
+
let keywordsJournal = game.journal.find(j => j.getFlag('${id}', 'systemJournal') === true);
|
|
191
|
+
|
|
192
|
+
if (!keywordsJournal) {
|
|
193
|
+
// Create the main system documentation journal
|
|
194
|
+
const journalData = {
|
|
195
|
+
name: "${systemLabel}",
|
|
196
|
+
folder: keywordsFolder.id,
|
|
197
|
+
flags: {
|
|
198
|
+
core: {
|
|
199
|
+
keywordsJournal: true // Legacy compatibility
|
|
200
|
+
},
|
|
201
|
+
'${id}': {
|
|
202
|
+
systemJournal: true,
|
|
203
|
+
version: game.system.version
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
keywordsJournal = await JournalEntry.create(journalData);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Separate keywords by type
|
|
212
|
+
const regularKeywords = {};
|
|
213
|
+
const damageTypes = {};
|
|
214
|
+
|
|
215
|
+
if (game.system.keywords) {
|
|
216
|
+
for (const [keywordKey, keywordData] of Object.entries(game.system.keywords)) {
|
|
217
|
+
if (keywordData.type === 'damage-type') {
|
|
218
|
+
damageTypes[keywordKey] = keywordData;
|
|
219
|
+
} else {
|
|
220
|
+
regularKeywords[keywordKey] = keywordData;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Use pre-collected status effects data with conditions
|
|
226
|
+
const statusEffects = ${JSON.stringify(statusEffectsData, null, 12).split('\n').join('\n ')};
|
|
227
|
+
|
|
228
|
+
// Check and create/update Keywords page
|
|
229
|
+
let keywordsPage = keywordsJournal.pages.find(p => p.getFlag('core', 'pageType') === 'keywords');
|
|
230
|
+
if (Object.keys(regularKeywords).length > 0) {
|
|
231
|
+
const keywordsContent = generateKeywordsPageContent(regularKeywords);
|
|
232
|
+
|
|
233
|
+
if (!keywordsPage) {
|
|
234
|
+
// Create new page
|
|
235
|
+
const keywordsPageData = {
|
|
236
|
+
name: game.i18n.localize("JOURNAL.Keywords"),
|
|
237
|
+
type: "text",
|
|
238
|
+
title: {
|
|
239
|
+
show: true,
|
|
240
|
+
level: 1
|
|
241
|
+
},
|
|
242
|
+
text: {
|
|
243
|
+
content: keywordsContent,
|
|
244
|
+
format: 1 // HTML format
|
|
245
|
+
},
|
|
246
|
+
flags: {
|
|
247
|
+
core: {
|
|
248
|
+
pageType: 'keywords'
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
await keywordsJournal.createEmbeddedDocuments("JournalEntryPage", [keywordsPageData]);
|
|
254
|
+
} else {
|
|
255
|
+
// Update existing page content
|
|
256
|
+
await keywordsPage.update({
|
|
257
|
+
"text.content": keywordsContent
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Check and create/update Damage Types page
|
|
263
|
+
let damageTypesPage = keywordsJournal.pages.find(p => p.getFlag('core', 'pageType') === 'damage-types');
|
|
264
|
+
if (Object.keys(damageTypes).length > 0) {
|
|
265
|
+
const damageTypesContent = generateDamageTypesPageContent(damageTypes);
|
|
266
|
+
|
|
267
|
+
if (!damageTypesPage) {
|
|
268
|
+
// Create new page
|
|
269
|
+
const damageTypesPageData = {
|
|
270
|
+
name: game.i18n.localize("JOURNAL.DamageTypes"),
|
|
271
|
+
type: "text",
|
|
272
|
+
title: {
|
|
273
|
+
show: true,
|
|
274
|
+
level: 1
|
|
275
|
+
},
|
|
276
|
+
text: {
|
|
277
|
+
content: damageTypesContent,
|
|
278
|
+
format: 1 // HTML format
|
|
279
|
+
},
|
|
280
|
+
flags: {
|
|
281
|
+
core: {
|
|
282
|
+
pageType: 'damage-types'
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
await keywordsJournal.createEmbeddedDocuments("JournalEntryPage", [damageTypesPageData]);
|
|
288
|
+
} else {
|
|
289
|
+
// Update existing page content
|
|
290
|
+
await damageTypesPage.update({
|
|
291
|
+
"text.content": damageTypesContent
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Check and create/update Status Effects page
|
|
297
|
+
let statusEffectsPage = keywordsJournal.pages.find(p => p.getFlag('core', 'pageType') === 'status-effects');
|
|
298
|
+
if (Object.keys(statusEffects).length > 0) {
|
|
299
|
+
const statusEffectsContent = generateStatusEffectsPageContent(statusEffects);
|
|
300
|
+
|
|
301
|
+
if (!statusEffectsPage) {
|
|
302
|
+
// Create new page
|
|
303
|
+
const statusEffectsPageData = {
|
|
304
|
+
name: game.i18n.localize("JOURNAL.StatusEffects"),
|
|
305
|
+
type: "text",
|
|
306
|
+
title: {
|
|
307
|
+
show: true,
|
|
308
|
+
level: 1
|
|
309
|
+
},
|
|
310
|
+
text: {
|
|
311
|
+
content: statusEffectsContent,
|
|
312
|
+
format: 1 // HTML format
|
|
313
|
+
},
|
|
314
|
+
flags: {
|
|
315
|
+
core: {
|
|
316
|
+
pageType: 'status-effects'
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
await keywordsJournal.createEmbeddedDocuments("JournalEntryPage", [statusEffectsPageData]);
|
|
322
|
+
} else {
|
|
323
|
+
// Update existing page content
|
|
324
|
+
await statusEffectsPage.update({
|
|
325
|
+
"text.content": statusEffectsContent
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function generateKeywordsPageContent(keywords) {
|
|
332
|
+
let content = \`<div class="isdl-journal-section">
|
|
333
|
+
<div class="isdl-journal-info-box">
|
|
334
|
+
<div class="isdl-journal-info-title">\${game.i18n.localize("JOURNAL.KeywordsUsageTitle")}</div>
|
|
335
|
+
<ul class="isdl-journal-info-list">
|
|
336
|
+
<li>\${game.i18n.localize("JOURNAL.KeywordsUsageList.GameMechanics")}</li>
|
|
337
|
+
<li>\${game.i18n.localize("JOURNAL.KeywordsUsageList.References")}</li>
|
|
338
|
+
<li>\${game.i18n.localize("JOURNAL.KeywordsUsageList.Documentation")}</li>
|
|
339
|
+
</ul>
|
|
340
|
+
</div>
|
|
341
|
+
</div>\`;
|
|
342
|
+
|
|
343
|
+
for (const [keywordKey, keyword] of Object.entries(keywords)) {
|
|
344
|
+
content += \`<div class="isdl-keyword-entry" id="\${keywordKey}" style="border-left-color: \${keyword.color};">
|
|
345
|
+
<div class="isdl-journal-header">
|
|
346
|
+
\${keyword.icon ? \`<i class="\${keyword.icon} isdl-journal-icon" style="color: \${keyword.color};"></i>\` : ''}
|
|
347
|
+
<h3 class="isdl-journal-title">\${keyword.name}</h3>
|
|
348
|
+
<span class="isdl-journal-badge" style="background: \${keyword.color}20;">\${game.i18n.localize("JOURNAL.KeywordBadge")}</span>
|
|
349
|
+
</div>
|
|
350
|
+
\${keyword.description ? \`<div class="isdl-journal-description"><p>\${keyword.description}</p></div>\` : ''}
|
|
351
|
+
</div>\`;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return content;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function generateDamageTypesPageContent(damageTypes) {
|
|
358
|
+
let content = \`<div class="isdl-journal-section">
|
|
359
|
+
<div class="isdl-journal-info-box">
|
|
360
|
+
<div class="isdl-journal-info-title">\${game.i18n.localize("JOURNAL.DamageTypeEffectsTitle")}</div>
|
|
361
|
+
<ul class="isdl-journal-info-list">
|
|
362
|
+
<li>\${game.i18n.localize("JOURNAL.DamageTypeEffectsList.Usage")}</li>
|
|
363
|
+
<li>\${game.i18n.localize("JOURNAL.DamageTypeEffectsList.Resistances")}</li>
|
|
364
|
+
<li>\${game.i18n.localize("JOURNAL.DamageTypeEffectsList.ChoiceFields")}</li>
|
|
365
|
+
</ul>
|
|
366
|
+
</div>
|
|
367
|
+
</div>\`;
|
|
368
|
+
|
|
369
|
+
for (const [keywordKey, keyword] of Object.entries(damageTypes)) {
|
|
370
|
+
content += \`<div class="isdl-damage-type-entry" id="\${keywordKey}" style="border-left-color: \${keyword.color};">
|
|
371
|
+
<div class="isdl-journal-header">
|
|
372
|
+
\${keyword.icon ? \`<i class="\${keyword.icon} isdl-journal-icon" style="color: \${keyword.color};"></i>\` : ''}
|
|
373
|
+
<h3 class="isdl-journal-title" style="color: \${keyword.color};">\${keyword.name}</h3>
|
|
374
|
+
<span class="isdl-journal-badge" style="background: \${keyword.color}20;">\${game.i18n.localize("JOURNAL.DamageTypeBadge")}</span>
|
|
375
|
+
</div>
|
|
376
|
+
\${keyword.description ? \`<div class="isdl-journal-description"><p>\${keyword.description}</p></div>\` : ''}
|
|
377
|
+
</div>\`;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return content;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function localizeConditionText(conditionText) {
|
|
384
|
+
if (!conditionText) return conditionText;
|
|
385
|
+
|
|
386
|
+
// Replace localization key patterns {{KEY}} with actual translations
|
|
387
|
+
return conditionText.replace(/\{\{([^}]+)\}\}/g, (match, key) => {
|
|
388
|
+
return game.i18n.localize(key);
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function generateStatusEffectsPageContent(statusEffects) {
|
|
393
|
+
let content = \`<div class="isdl-journal-section">
|
|
394
|
+
<div class="isdl-journal-info-box">
|
|
395
|
+
<div class="isdl-journal-info-title">\${game.i18n.localize("JOURNAL.StatusEffectUsageTitle")}</div>
|
|
396
|
+
<ul class="isdl-journal-info-list">
|
|
397
|
+
<li>\${game.i18n.localize("JOURNAL.StatusEffectUsageList.TokenApplication")}</li>
|
|
398
|
+
<li>\${game.i18n.localize("JOURNAL.StatusEffectUsageList.MenuAppearance")}</li>
|
|
399
|
+
<li>\${game.i18n.localize("JOURNAL.StatusEffectUsageList.VisualIndicators")}</li>
|
|
400
|
+
</ul>
|
|
401
|
+
</div>
|
|
402
|
+
</div>\`;
|
|
403
|
+
for (const [statusId, statusEffect] of Object.entries(statusEffects)) {
|
|
404
|
+
const isDeathEffect = statusEffect.isDeath || statusId.toLowerCase().includes('dead');
|
|
405
|
+
const effectColor = isDeathEffect ? '#dc2626' : '#4f46e5';
|
|
406
|
+
const effectBadge = isDeathEffect ? game.i18n.localize("JOURNAL.DeathEffectBadge") : game.i18n.localize("JOURNAL.StatusEffectBadge");
|
|
407
|
+
|
|
408
|
+
content += \`<div class="isdl-status-effect-entry" id="\${statusId}" style="border-left-color: \${effectColor};">
|
|
409
|
+
<div class="isdl-journal-header">
|
|
410
|
+
\${statusEffect.img ? \`<img src="\${statusEffect.img}" alt="\${statusEffect.name}" class="isdl-status-effect-image" />\` : ''}
|
|
411
|
+
<h3 class="isdl-journal-title" style="color: \${effectColor};">\${statusEffect.name}</h3>
|
|
412
|
+
<span class="isdl-journal-badge" style="background: \${effectColor}20; color: \${effectColor};">\${effectBadge}</span>
|
|
413
|
+
</div>
|
|
414
|
+
\${statusEffect.condition ? \`<div class="isdl-journal-condition">
|
|
415
|
+
<p><span class="isdl-journal-condition-label">\${game.i18n.localize("JOURNAL.AppliedWhen")}</span> \${localizeConditionText(statusEffect.condition)}</p>
|
|
416
|
+
</div>\` : ''}
|
|
417
|
+
</div>\`;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return content;
|
|
421
|
+
}
|
|
422
422
|
`.appendNewLineIfNotEmpty();
|
|
423
423
|
fs.writeFileSync(generatedFilePath, toString(fileNode));
|
|
424
424
|
}
|