@carmaclouds/core 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cache/CacheManager.d.ts.map +1 -0
- package/dist/cache/CacheManager.js +131 -0
- package/dist/cache/CacheManager.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/ir/index.d.ts +11 -0
- package/dist/ir/index.d.ts.map +1 -0
- package/dist/ir/index.js +9 -0
- package/dist/ir/index.js.map +1 -0
- package/dist/ir/normalize.d.ts +10 -0
- package/dist/ir/normalize.d.ts.map +1 -0
- package/dist/ir/normalize.js +207 -0
- package/dist/ir/normalize.js.map +1 -0
- package/dist/ir/persistence.d.ts +26 -0
- package/dist/ir/persistence.d.ts.map +1 -0
- package/dist/ir/persistence.js +21 -0
- package/dist/ir/persistence.js.map +1 -0
- package/dist/ir/sync.d.ts +12 -0
- package/dist/ir/sync.d.ts.map +1 -0
- package/dist/ir/sync.js +36 -0
- package/dist/ir/sync.js.map +1 -0
- package/dist/ir/types.d.ts +143 -0
- package/dist/ir/types.d.ts.map +1 -0
- package/dist/ir/types.js +13 -0
- package/dist/ir/types.js.map +1 -0
- package/dist/ir/views/dnd5e.d.ts +40 -0
- package/dist/ir/views/dnd5e.d.ts.map +1 -0
- package/dist/ir/views/dnd5e.js +50 -0
- package/dist/ir/views/dnd5e.js.map +1 -0
- package/dist/render/character.d.ts +19 -0
- package/dist/render/character.d.ts.map +1 -0
- package/dist/render/character.js +156 -0
- package/dist/render/character.js.map +1 -0
- package/dist/render/h.d.ts +27 -0
- package/dist/render/h.d.ts.map +1 -0
- package/dist/render/h.js +64 -0
- package/dist/render/h.js.map +1 -0
- package/dist/render/index.d.ts +11 -0
- package/dist/render/index.d.ts.map +1 -0
- package/dist/render/index.js +8 -0
- package/dist/render/index.js.map +1 -0
- package/dist/render/mount.d.ts +31 -0
- package/dist/render/mount.d.ts.map +1 -0
- package/dist/render/mount.js +63 -0
- package/dist/render/mount.js.map +1 -0
- package/dist/supabase/fields.d.ts.map +1 -0
- package/dist/supabase/fields.js +120 -0
- package/dist/supabase/fields.js.map +1 -0
- package/dist/types/character.d.ts.map +1 -0
- package/dist/types/character.js +5 -0
- package/dist/types/character.js.map +1 -0
- package/package.json +73 -0
- package/src/browser.js +51 -0
- package/src/cache/CacheManager.ts +174 -0
- package/src/common/browser-polyfill.js +319 -0
- package/src/common/debug.js +123 -0
- package/src/common/html-utils.js +134 -0
- package/src/common/theme-manager.js +265 -0
- package/src/index.ts +25 -0
- package/src/ir/__fixtures__/dnd5e-character.json +75962 -0
- package/src/ir/__fixtures__/non-dnd-character.json +14218 -0
- package/src/ir/index.ts +10 -0
- package/src/ir/normalize.ts +245 -0
- package/src/ir/persistence.ts +37 -0
- package/src/ir/sync.ts +49 -0
- package/src/ir/types.ts +161 -0
- package/src/ir/views/dnd5e.ts +94 -0
- package/src/lib/indexeddb-cache.js +320 -0
- package/src/modules/action-announcements.js +102 -0
- package/src/modules/action-display.js +1557 -0
- package/src/modules/action-executor.js +860 -0
- package/src/modules/action-filters.js +167 -0
- package/src/modules/action-options.js +117 -0
- package/src/modules/card-creator.js +142 -0
- package/src/modules/character-portrait.js +169 -0
- package/src/modules/character-trait-popups.js +959 -0
- package/src/modules/character-traits.js +814 -0
- package/src/modules/class-feature-edge-cases.js +1320 -0
- package/src/modules/color-utils.js +69 -0
- package/src/modules/combat-maneuver-edge-cases.js +660 -0
- package/src/modules/companions-manager.js +178 -0
- package/src/modules/concentration-tracker.js +178 -0
- package/src/modules/data-manager.js +514 -0
- package/src/modules/dice-roller.js +719 -0
- package/src/modules/effects-manager.js +743 -0
- package/src/modules/feature-modals.js +1264 -0
- package/src/modules/formula-resolver.js +444 -0
- package/src/modules/gm-mode.js +184 -0
- package/src/modules/health-modals.js +399 -0
- package/src/modules/hp-management.js +752 -0
- package/src/modules/inventory-manager.js +242 -0
- package/src/modules/macro-system.js +825 -0
- package/src/modules/notification-system.js +92 -0
- package/src/modules/racial-feature-edge-cases.js +746 -0
- package/src/modules/resource-manager.js +775 -0
- package/src/modules/sheet-builder.js +654 -0
- package/src/modules/spell-action-modals.js +583 -0
- package/src/modules/spell-cards.js +602 -0
- package/src/modules/spell-casting.js +723 -0
- package/src/modules/spell-display.js +314 -0
- package/src/modules/spell-edge-cases.js +509 -0
- package/src/modules/spell-macros.js +201 -0
- package/src/modules/spell-modals.js +1221 -0
- package/src/modules/spell-slots.js +224 -0
- package/src/modules/status-bar-bridge.js +101 -0
- package/src/modules/ui-utilities.js +284 -0
- package/src/modules/warlock-invocations.js +219 -0
- package/src/modules/window-management.js +211 -0
- package/src/render/character.ts +234 -0
- package/src/render/h.ts +74 -0
- package/src/render/index.ts +10 -0
- package/src/render/mount.ts +94 -0
- package/src/supabase/client.js +1383 -0
- package/src/supabase/config.js +60 -0
- package/src/supabase/fields.ts +129 -0
- package/src/types/character.ts +85 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spell Display Module
|
|
3
|
+
*
|
|
4
|
+
* Handles spell list display, organization, and filtering.
|
|
5
|
+
* - Builds spell list organized by level
|
|
6
|
+
* - Applies filters (level, category, casting time, search)
|
|
7
|
+
* - Categorizes spells (damage/healing/utility)
|
|
8
|
+
*
|
|
9
|
+
* Loaded as a plain script (no ES6 modules) to export to window.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
(function() {
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Build and display spells organized by level
|
|
17
|
+
* @param {HTMLElement} container - Container element for spells
|
|
18
|
+
* @param {Array} spells - Array of spell objects
|
|
19
|
+
*/
|
|
20
|
+
function buildSpellsBySource(container, spells) {
|
|
21
|
+
if (typeof characterData === 'undefined' || !characterData) {
|
|
22
|
+
console.error('characterData not available');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const debug = window.debug || console;
|
|
27
|
+
debug.log(`📚 buildSpellsBySource called with ${spells.length} spells`);
|
|
28
|
+
debug.log(`📚 Spell names: ${spells.map(s => s.name).join(', ')}`);
|
|
29
|
+
|
|
30
|
+
// Debug: Check for Eldritch Blast damageRolls
|
|
31
|
+
const eldritchBlast = spells.find(s => s.name && s.name.toLowerCase().includes('eldritch blast'));
|
|
32
|
+
if (eldritchBlast) {
|
|
33
|
+
console.log('⚡ ELDRITCH BLAST DATA IN POPUP:', {
|
|
34
|
+
name: eldritchBlast.name,
|
|
35
|
+
attackRoll: eldritchBlast.attackRoll,
|
|
36
|
+
damageRolls: eldritchBlast.damageRolls,
|
|
37
|
+
damageRollsLength: eldritchBlast.damageRolls ? eldritchBlast.damageRolls.length : 'undefined',
|
|
38
|
+
damageRollsJSON: JSON.stringify(eldritchBlast.damageRolls)
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Apply filters first
|
|
43
|
+
let filteredSpells = spells.filter(spell => {
|
|
44
|
+
// Filter out duplicate Divine Smite entries - keep only the main one
|
|
45
|
+
const spellName = (spell.name || '').toLowerCase();
|
|
46
|
+
if (spellName.includes('divine smite')) {
|
|
47
|
+
// Skip variants like "Divine Smite Level 1", "Divine Smite (Against Fiends, Critical) Level 1", etc.
|
|
48
|
+
// Keep only the base "Divine Smite" entry
|
|
49
|
+
if (spellName !== 'divine smite' && !spellName.match(/^divine smite$/)) {
|
|
50
|
+
debug.log(`⏭️ Filtering out duplicate Divine Smite spell: ${spell.name}`);
|
|
51
|
+
return false;
|
|
52
|
+
} else {
|
|
53
|
+
debug.log(`✅ Keeping main Divine Smite spell: ${spell.name}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Filter by spell level
|
|
58
|
+
if (window.spellFilters && window.spellFilters.level !== 'all') {
|
|
59
|
+
const spellLevel = parseInt(spell.level) || 0;
|
|
60
|
+
if (spellLevel.toString() !== window.spellFilters.level) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Filter by category
|
|
66
|
+
if (window.spellFilters && window.spellFilters.category !== 'all') {
|
|
67
|
+
const category = categorizeSpell(spell);
|
|
68
|
+
if (category !== window.spellFilters.category) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Filter by casting time
|
|
74
|
+
if (window.spellFilters && window.spellFilters.castingTime !== 'all') {
|
|
75
|
+
const castingTime = (spell.castingTime || '').toLowerCase();
|
|
76
|
+
if (window.spellFilters.castingTime === 'action') {
|
|
77
|
+
// Match "action" but exclude "bonus action" and "reaction"
|
|
78
|
+
if (!castingTime.includes('action') || castingTime.includes('bonus') || castingTime.includes('reaction')) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (window.spellFilters.castingTime === 'bonus' && !castingTime.includes('bonus')) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
if (window.spellFilters.castingTime === 'reaction' && !castingTime.includes('reaction')) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Filter by search term
|
|
91
|
+
if (window.spellFilters && window.spellFilters.search) {
|
|
92
|
+
const searchLower = window.spellFilters.search;
|
|
93
|
+
const name = (spell.name || '').toLowerCase();
|
|
94
|
+
const desc = (spell.description || '').toLowerCase();
|
|
95
|
+
if (!name.includes(searchLower) && !desc.includes(searchLower)) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return true;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
debug.log(`🔍 Filtered ${spells.length} spells to ${filteredSpells.length} spells`);
|
|
104
|
+
|
|
105
|
+
// Group spells by actual spell level (not source)
|
|
106
|
+
const spellsByLevel = {};
|
|
107
|
+
|
|
108
|
+
filteredSpells.forEach((spell, index) => {
|
|
109
|
+
// Add index to spell for tracking
|
|
110
|
+
spell.index = index;
|
|
111
|
+
|
|
112
|
+
// Use spell level for grouping
|
|
113
|
+
const spellLevel = parseInt(spell.level) || 0;
|
|
114
|
+
const levelKey = spellLevel === 0 ? 'Cantrips' : `Level ${spellLevel} Spells`;
|
|
115
|
+
|
|
116
|
+
if (!spellsByLevel[levelKey]) {
|
|
117
|
+
spellsByLevel[levelKey] = [];
|
|
118
|
+
}
|
|
119
|
+
spellsByLevel[levelKey].push(spell);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Clear container
|
|
123
|
+
container.innerHTML = '';
|
|
124
|
+
|
|
125
|
+
// Sort by spell level (cantrips first, then 1-9)
|
|
126
|
+
const sortedLevels = Object.keys(spellsByLevel).sort((a, b) => {
|
|
127
|
+
if (a === 'Cantrips') return -1;
|
|
128
|
+
if (b === 'Cantrips') return 1;
|
|
129
|
+
return a.localeCompare(b, undefined, { numeric: true });
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
sortedLevels.forEach(levelKey => {
|
|
133
|
+
// Create level section
|
|
134
|
+
const levelSection = document.createElement('div');
|
|
135
|
+
levelSection.style.cssText = 'margin-bottom: 20px;';
|
|
136
|
+
|
|
137
|
+
const levelHeader = document.createElement('h4');
|
|
138
|
+
levelHeader.textContent = `📚 ${levelKey}`;
|
|
139
|
+
levelHeader.style.cssText = 'color: var(--text-primary); margin-bottom: 10px; padding: 5px; background: var(--bg-secondary, #ecf0f1); border-radius: 4px;';
|
|
140
|
+
levelSection.appendChild(levelHeader);
|
|
141
|
+
|
|
142
|
+
// Sort spells alphabetically within level
|
|
143
|
+
const sortedSpells = spellsByLevel[levelKey].sort((a, b) => {
|
|
144
|
+
return (a.name || '').localeCompare(b.name || '');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Deduplicate spells by name and combine sources
|
|
148
|
+
const deduplicatedSpells = [];
|
|
149
|
+
const spellsByName = {};
|
|
150
|
+
|
|
151
|
+
debug.log(`📚 Deduplicating ${sortedSpells.length} spells in ${levelKey}`, sortedSpells.map(s => s.name));
|
|
152
|
+
sortedSpells.forEach(spell => {
|
|
153
|
+
const spellName = spell.name || 'Unnamed Spell';
|
|
154
|
+
|
|
155
|
+
if (!spellsByName[spellName]) {
|
|
156
|
+
// First occurrence of this spell
|
|
157
|
+
spellsByName[spellName] = spell;
|
|
158
|
+
deduplicatedSpells.push(spell);
|
|
159
|
+
debug.log(`📚 First occurrence: "${spellName}"`);
|
|
160
|
+
} else {
|
|
161
|
+
// Duplicate spell - combine sources
|
|
162
|
+
const existingSpell = spellsByName[spellName];
|
|
163
|
+
debug.log(`📚 Found duplicate: "${spellName}" - combining sources`);
|
|
164
|
+
if (spell.source && !existingSpell.source.includes(spell.source)) {
|
|
165
|
+
existingSpell.source += '; ' + spell.source;
|
|
166
|
+
debug.log(`📚 Combined duplicate spell "${spellName}": ${existingSpell.source}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
debug.log(`📚 After deduplication: ${deduplicatedSpells.length} unique spells in ${levelKey}`, deduplicatedSpells.map(s => s.name));
|
|
171
|
+
|
|
172
|
+
// Add deduplicated spells
|
|
173
|
+
deduplicatedSpells.forEach(spell => {
|
|
174
|
+
if (typeof createSpellCard === 'function') {
|
|
175
|
+
const spellCard = createSpellCard(spell, spell.index);
|
|
176
|
+
levelSection.appendChild(spellCard);
|
|
177
|
+
} else {
|
|
178
|
+
debug.warn('createSpellCard function not available');
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
container.appendChild(levelSection);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Rebuild spells with current filters
|
|
188
|
+
*/
|
|
189
|
+
function rebuildSpells() {
|
|
190
|
+
if (!characterData || !characterData.spells) return;
|
|
191
|
+
const container = document.getElementById('spells-container');
|
|
192
|
+
buildSpellsBySource(container, characterData.spells);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Categorize a spell as damage, healing, or utility
|
|
197
|
+
* @param {object} spell - Spell object
|
|
198
|
+
* @returns {string} Category: 'damage', 'healing', or 'utility'
|
|
199
|
+
*/
|
|
200
|
+
function categorizeSpell(spell) {
|
|
201
|
+
const spellName = (spell.name || '').toLowerCase();
|
|
202
|
+
|
|
203
|
+
// Check spell name for common healing keywords first (most reliable)
|
|
204
|
+
const healingKeywords = ['cure', 'heal', 'regenerat', 'revivif', 'raise', 'resurrection', 'aid', 'prayer of healing', 'mass healing'];
|
|
205
|
+
const isHealingSpell = healingKeywords.some(keyword => spellName.includes(keyword));
|
|
206
|
+
|
|
207
|
+
if (isHealingSpell) {
|
|
208
|
+
return 'healing';
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Use actual spell data instead of string matching in description
|
|
212
|
+
// Check damageRolls array to determine if it's damage or healing
|
|
213
|
+
if (spell.damageRolls && Array.isArray(spell.damageRolls) && spell.damageRolls.length > 0) {
|
|
214
|
+
// Check if any damage roll is healing
|
|
215
|
+
const hasHealing = spell.damageRolls.some(roll =>
|
|
216
|
+
roll.damageType && roll.damageType.toLowerCase() === 'healing'
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
// Check if any damage roll is actual damage (not healing)
|
|
220
|
+
const hasDamage = spell.damageRolls.some(roll =>
|
|
221
|
+
!roll.damageType || roll.damageType.toLowerCase() !== 'healing'
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
// Categorize based on what the spell actually does
|
|
225
|
+
if (hasHealing && !hasDamage) {
|
|
226
|
+
return 'healing';
|
|
227
|
+
} else if (hasDamage) {
|
|
228
|
+
return 'damage';
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Check legacy damage field with damageType
|
|
233
|
+
if (spell.damage && spell.damageType) {
|
|
234
|
+
if (spell.damageType.toLowerCase() === 'healing') {
|
|
235
|
+
return 'healing';
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Check for attack roll (attack spells are damage)
|
|
240
|
+
if (spell.attackRoll && spell.attackRoll !== '(none)') {
|
|
241
|
+
return 'damage';
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Everything else is utility (no damage rolls, no attack roll)
|
|
245
|
+
return 'utility';
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Initialize spell filter event listeners
|
|
250
|
+
*/
|
|
251
|
+
function initializeSpellFilters() {
|
|
252
|
+
// Initialize spell filters object
|
|
253
|
+
if (!window.spellFilters) {
|
|
254
|
+
window.spellFilters = {
|
|
255
|
+
level: 'all',
|
|
256
|
+
category: 'all',
|
|
257
|
+
castingTime: 'all',
|
|
258
|
+
search: ''
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Spell search filter
|
|
263
|
+
const spellsSearch = document.getElementById('spells-search');
|
|
264
|
+
if (spellsSearch) {
|
|
265
|
+
spellsSearch.addEventListener('input', (e) => {
|
|
266
|
+
window.spellFilters.search = e.target.value.toLowerCase();
|
|
267
|
+
rebuildSpells();
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Spell level filters
|
|
272
|
+
document.querySelectorAll('[data-type="spell-level"]').forEach(btn => {
|
|
273
|
+
btn.addEventListener('click', () => {
|
|
274
|
+
window.spellFilters.level = btn.dataset.filter;
|
|
275
|
+
document.querySelectorAll('[data-type="spell-level"]').forEach(b => b.classList.remove('active'));
|
|
276
|
+
btn.classList.add('active');
|
|
277
|
+
rebuildSpells();
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Spell category filters
|
|
282
|
+
document.querySelectorAll('[data-type="spell-category"]').forEach(btn => {
|
|
283
|
+
btn.addEventListener('click', () => {
|
|
284
|
+
window.spellFilters.category = btn.dataset.filter;
|
|
285
|
+
document.querySelectorAll('[data-type="spell-category"]').forEach(b => b.classList.remove('active'));
|
|
286
|
+
btn.classList.add('active');
|
|
287
|
+
rebuildSpells();
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// Spell casting time filters
|
|
292
|
+
document.querySelectorAll('[data-type="spell-casting-time"]').forEach(btn => {
|
|
293
|
+
btn.addEventListener('click', () => {
|
|
294
|
+
window.spellFilters.castingTime = btn.dataset.filter;
|
|
295
|
+
document.querySelectorAll('[data-type="spell-casting-time"]').forEach(b => b.classList.remove('active'));
|
|
296
|
+
btn.classList.add('active');
|
|
297
|
+
rebuildSpells();
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
debug.log('✅ Spell filters initialized');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Export functions to globalThis
|
|
305
|
+
Object.assign(globalThis, {
|
|
306
|
+
buildSpellsBySource,
|
|
307
|
+
rebuildSpells,
|
|
308
|
+
categorizeSpell,
|
|
309
|
+
initializeSpellFilters
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
console.log('✅ Spell Display module loaded');
|
|
313
|
+
|
|
314
|
+
})();
|