@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,825 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Macro System Module
|
|
3
|
+
*
|
|
4
|
+
* Handles custom roll macros - creation, storage, execution, and settings UI.
|
|
5
|
+
* Includes the full settings modal with tabs for macros and preferences.
|
|
6
|
+
*
|
|
7
|
+
* Loaded as a plain script (no ES6 modules) to export to globalThis.
|
|
8
|
+
*
|
|
9
|
+
* Functions exported to globalThis:
|
|
10
|
+
* - loadCustomMacros()
|
|
11
|
+
* - saveAllCustomMacros()
|
|
12
|
+
* - addCustomMacro(name, formula, description)
|
|
13
|
+
* - deleteCustomMacro(macroId)
|
|
14
|
+
* - executeMacro(macro)
|
|
15
|
+
* - updateMacrosDisplay()
|
|
16
|
+
* - showSettingsModal()
|
|
17
|
+
* - showAddMacroModal()
|
|
18
|
+
* - updateMacrosDisplayInSettings()
|
|
19
|
+
* - initCustomMacros()
|
|
20
|
+
* - initSettingsButton()
|
|
21
|
+
*
|
|
22
|
+
* State variables exported to globalThis:
|
|
23
|
+
* - customMacros
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
(function() {
|
|
27
|
+
'use strict';
|
|
28
|
+
|
|
29
|
+
// ===== STATE =====
|
|
30
|
+
|
|
31
|
+
// Setting to show/hide custom macro buttons on spells
|
|
32
|
+
let showCustomMacroButtons = false;
|
|
33
|
+
|
|
34
|
+
let customMacros = [];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Load custom macros from storage
|
|
38
|
+
*/
|
|
39
|
+
async function loadCustomMacros() {
|
|
40
|
+
try {
|
|
41
|
+
const result = await browserAPI.storage.local.get(['customMacros']);
|
|
42
|
+
customMacros = result.customMacros || [];
|
|
43
|
+
debug.log(`🎲 Loaded ${customMacros.length} custom macros`);
|
|
44
|
+
updateMacrosDisplay();
|
|
45
|
+
} catch (error) {
|
|
46
|
+
debug.error('❌ Failed to load custom macros:', error);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Save all custom macros to storage
|
|
52
|
+
*/
|
|
53
|
+
function saveAllCustomMacros() {
|
|
54
|
+
browserAPI.storage.local.set({ customMacros });
|
|
55
|
+
debug.log(`💾 Saved ${customMacros.length} custom macros`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Add a new custom macro
|
|
60
|
+
*/
|
|
61
|
+
function addCustomMacro(name, formula, description = '') {
|
|
62
|
+
const macro = {
|
|
63
|
+
id: Date.now().toString(),
|
|
64
|
+
name,
|
|
65
|
+
formula,
|
|
66
|
+
description,
|
|
67
|
+
createdAt: Date.now()
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
customMacros.push(macro);
|
|
71
|
+
saveAllCustomMacros();
|
|
72
|
+
updateMacrosDisplay();
|
|
73
|
+
|
|
74
|
+
debug.log(`✅ Added custom macro: ${name} (${formula})`);
|
|
75
|
+
return macro;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Delete a custom macro
|
|
80
|
+
*/
|
|
81
|
+
function deleteCustomMacro(macroId) {
|
|
82
|
+
customMacros = customMacros.filter(m => m.id !== macroId);
|
|
83
|
+
saveAllCustomMacros();
|
|
84
|
+
updateMacrosDisplay();
|
|
85
|
+
debug.log(`🗑️ Deleted macro: ${macroId}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Execute a custom macro (roll the formula)
|
|
90
|
+
*/
|
|
91
|
+
function executeMacro(macro) {
|
|
92
|
+
debug.log(`🎲 Executing macro: ${macro.name} (${macro.formula})`);
|
|
93
|
+
|
|
94
|
+
// Use the existing roll announcement system
|
|
95
|
+
announceAction(
|
|
96
|
+
macro.name,
|
|
97
|
+
macro.formula,
|
|
98
|
+
'',
|
|
99
|
+
macro.description || `Custom macro: ${macro.formula}`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Update the macros display in the UI
|
|
105
|
+
*/
|
|
106
|
+
function updateMacrosDisplay() {
|
|
107
|
+
const container = document.getElementById('custom-macros-container');
|
|
108
|
+
if (!container) return;
|
|
109
|
+
|
|
110
|
+
if (customMacros.length === 0) {
|
|
111
|
+
container.innerHTML = '<p style="text-align: center; color: #888; padding: 15px;">No custom macros yet. Click "Add Macro" to create one.</p>';
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
container.innerHTML = customMacros.map(macro => {
|
|
116
|
+
// Validate macro properties to prevent undefined display
|
|
117
|
+
const macroName = macro.name || 'Unnamed Macro';
|
|
118
|
+
const macroFormula = macro.formula || '';
|
|
119
|
+
const macroDescription = macro.description || '';
|
|
120
|
+
|
|
121
|
+
return `
|
|
122
|
+
<div class="macro-item" style="
|
|
123
|
+
background: var(--bg-secondary);
|
|
124
|
+
border: 2px solid var(--border-color);
|
|
125
|
+
border-radius: 8px;
|
|
126
|
+
padding: 12px;
|
|
127
|
+
margin-bottom: 10px;
|
|
128
|
+
display: flex;
|
|
129
|
+
justify-content: space-between;
|
|
130
|
+
align-items: center;
|
|
131
|
+
transition: all 0.2s;
|
|
132
|
+
">
|
|
133
|
+
<div style="flex: 1;">
|
|
134
|
+
<div style="font-weight: bold; color: var(--text-primary); margin-bottom: 4px;">
|
|
135
|
+
${macroName}
|
|
136
|
+
</div>
|
|
137
|
+
<div style="font-family: monospace; color: var(--accent-info); font-size: 0.9em; margin-bottom: 4px;">
|
|
138
|
+
${macroFormula}
|
|
139
|
+
</div>
|
|
140
|
+
${macroDescription ? `<div style="font-size: 0.85em; color: var(--text-secondary);">${macroDescription}</div>` : ''}
|
|
141
|
+
</div>
|
|
142
|
+
<div style="display: flex; gap: 8px;">
|
|
143
|
+
<button class="macro-roll-btn" data-macro-id="${macro.id}" style="
|
|
144
|
+
background: var(--accent-primary);
|
|
145
|
+
color: white;
|
|
146
|
+
border: none;
|
|
147
|
+
padding: 8px 16px;
|
|
148
|
+
border-radius: 6px;
|
|
149
|
+
cursor: pointer;
|
|
150
|
+
font-weight: bold;
|
|
151
|
+
transition: all 0.2s;
|
|
152
|
+
">
|
|
153
|
+
🎲 Roll
|
|
154
|
+
</button>
|
|
155
|
+
<button class="macro-delete-btn" data-macro-id="${macro.id}" style="
|
|
156
|
+
background: var(--accent-danger);
|
|
157
|
+
color: white;
|
|
158
|
+
border: none;
|
|
159
|
+
padding: 8px 12px;
|
|
160
|
+
border-radius: 6px;
|
|
161
|
+
cursor: pointer;
|
|
162
|
+
transition: all 0.2s;
|
|
163
|
+
">
|
|
164
|
+
🗑️
|
|
165
|
+
</button>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
`;
|
|
169
|
+
}).join('');
|
|
170
|
+
|
|
171
|
+
// Add event listeners
|
|
172
|
+
container.querySelectorAll('.macro-roll-btn').forEach(btn => {
|
|
173
|
+
btn.addEventListener('click', () => {
|
|
174
|
+
const macroId = btn.dataset.macroId;
|
|
175
|
+
const macro = customMacros.find(m => m.id === macroId);
|
|
176
|
+
if (macro) executeMacro(macro);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
container.querySelectorAll('.macro-delete-btn').forEach(btn => {
|
|
181
|
+
btn.addEventListener('click', () => {
|
|
182
|
+
const macroId = btn.dataset.macroId;
|
|
183
|
+
const macro = customMacros.find(m => m.id === macroId);
|
|
184
|
+
if (macro && confirm(`Delete macro "${macro.name}"?`)) {
|
|
185
|
+
deleteCustomMacro(macroId);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Show settings modal with tabs
|
|
193
|
+
*/
|
|
194
|
+
function showSettingsModal() {
|
|
195
|
+
// Remove existing modal if any
|
|
196
|
+
const existingModal = document.getElementById('settings-modal');
|
|
197
|
+
if (existingModal) {
|
|
198
|
+
document.body.removeChild(existingModal);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const modal = document.createElement('div');
|
|
202
|
+
modal.id = 'settings-modal';
|
|
203
|
+
modal.style.cssText = `
|
|
204
|
+
position: fixed;
|
|
205
|
+
top: 0;
|
|
206
|
+
left: 0;
|
|
207
|
+
width: 100%;
|
|
208
|
+
height: 100%;
|
|
209
|
+
background: rgba(0, 0, 0, 0.7);
|
|
210
|
+
display: flex;
|
|
211
|
+
align-items: center;
|
|
212
|
+
justify-content: center;
|
|
213
|
+
z-index: 10000;
|
|
214
|
+
`;
|
|
215
|
+
|
|
216
|
+
modal.innerHTML = `
|
|
217
|
+
<div style="
|
|
218
|
+
background: var(--bg-secondary);
|
|
219
|
+
border: 2px solid var(--border-color);
|
|
220
|
+
border-radius: 12px;
|
|
221
|
+
max-width: 700px;
|
|
222
|
+
width: 90%;
|
|
223
|
+
max-height: 80vh;
|
|
224
|
+
display: flex;
|
|
225
|
+
flex-direction: column;
|
|
226
|
+
overflow: hidden;
|
|
227
|
+
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
|
|
228
|
+
">
|
|
229
|
+
<!-- Header -->
|
|
230
|
+
<div style="
|
|
231
|
+
padding: 20px;
|
|
232
|
+
border-bottom: 2px solid var(--border-color);
|
|
233
|
+
display: flex;
|
|
234
|
+
justify-content: space-between;
|
|
235
|
+
align-items: center;
|
|
236
|
+
">
|
|
237
|
+
<h3 style="margin: 0; color: var(--text-primary);">⚙️ Settings</h3>
|
|
238
|
+
<button id="settings-close-btn" style="
|
|
239
|
+
background: var(--accent-danger);
|
|
240
|
+
color: white;
|
|
241
|
+
border: none;
|
|
242
|
+
padding: 6px 12px;
|
|
243
|
+
border-radius: 6px;
|
|
244
|
+
cursor: pointer;
|
|
245
|
+
font-weight: bold;
|
|
246
|
+
">✕</button>
|
|
247
|
+
</div>
|
|
248
|
+
|
|
249
|
+
<!-- Tabs -->
|
|
250
|
+
<div style="
|
|
251
|
+
display: flex;
|
|
252
|
+
border-bottom: 2px solid var(--border-color);
|
|
253
|
+
background: var(--bg-tertiary);
|
|
254
|
+
">
|
|
255
|
+
<button class="settings-tab active" data-tab="theme" style="
|
|
256
|
+
flex: 1;
|
|
257
|
+
padding: 12px;
|
|
258
|
+
background: transparent;
|
|
259
|
+
border: none;
|
|
260
|
+
cursor: pointer;
|
|
261
|
+
font-weight: bold;
|
|
262
|
+
color: var(--text-secondary);
|
|
263
|
+
transition: all 0.2s;
|
|
264
|
+
">🎨 Theme</button>
|
|
265
|
+
<button class="settings-tab" data-tab="macros" style="
|
|
266
|
+
flex: 1;
|
|
267
|
+
padding: 12px;
|
|
268
|
+
background: transparent;
|
|
269
|
+
border: none;
|
|
270
|
+
cursor: pointer;
|
|
271
|
+
font-weight: bold;
|
|
272
|
+
color: var(--text-secondary);
|
|
273
|
+
transition: all 0.2s;
|
|
274
|
+
">🎲 Custom Macros</button>
|
|
275
|
+
<button class="settings-tab" data-tab="gm" style="
|
|
276
|
+
flex: 1;
|
|
277
|
+
padding: 12px;
|
|
278
|
+
background: transparent;
|
|
279
|
+
border: none;
|
|
280
|
+
cursor: pointer;
|
|
281
|
+
font-weight: bold;
|
|
282
|
+
color: var(--text-secondary);
|
|
283
|
+
transition: all 0.2s;
|
|
284
|
+
">👑 GM Integration</button>
|
|
285
|
+
</div>
|
|
286
|
+
|
|
287
|
+
<!-- Content -->
|
|
288
|
+
<div style="
|
|
289
|
+
flex: 1;
|
|
290
|
+
overflow-y: auto;
|
|
291
|
+
padding: 20px;
|
|
292
|
+
">
|
|
293
|
+
<!-- Theme Tab -->
|
|
294
|
+
<div id="theme-tab-content" class="settings-tab-content">
|
|
295
|
+
<h4 style="margin: 0 0 15px 0; color: var(--text-primary);">Choose Theme</h4>
|
|
296
|
+
<div style="display: flex; gap: 15px; margin-bottom: 20px;">
|
|
297
|
+
<button class="theme-option" data-theme="light" style="
|
|
298
|
+
flex: 1;
|
|
299
|
+
padding: 20px;
|
|
300
|
+
background: var(--bg-primary);
|
|
301
|
+
border: 3px solid var(--border-color);
|
|
302
|
+
border-radius: 8px;
|
|
303
|
+
cursor: pointer;
|
|
304
|
+
transition: all 0.2s;
|
|
305
|
+
display: flex;
|
|
306
|
+
flex-direction: column;
|
|
307
|
+
align-items: center;
|
|
308
|
+
gap: 8px;
|
|
309
|
+
">
|
|
310
|
+
<span style="font-size: 2em;">☀️</span>
|
|
311
|
+
<span style="font-weight: bold; color: var(--text-primary);">Light</span>
|
|
312
|
+
</button>
|
|
313
|
+
<button class="theme-option" data-theme="dark" style="
|
|
314
|
+
flex: 1;
|
|
315
|
+
padding: 20px;
|
|
316
|
+
background: var(--bg-primary);
|
|
317
|
+
border: 3px solid var(--border-color);
|
|
318
|
+
border-radius: 8px;
|
|
319
|
+
cursor: pointer;
|
|
320
|
+
transition: all 0.2s;
|
|
321
|
+
display: flex;
|
|
322
|
+
flex-direction: column;
|
|
323
|
+
align-items: center;
|
|
324
|
+
gap: 8px;
|
|
325
|
+
">
|
|
326
|
+
<span style="font-size: 2em;">🌙</span>
|
|
327
|
+
<span style="font-weight: bold; color: var(--text-primary);">Dark</span>
|
|
328
|
+
</button>
|
|
329
|
+
<button class="theme-option active" data-theme="system" style="
|
|
330
|
+
flex: 1;
|
|
331
|
+
padding: 20px;
|
|
332
|
+
background: var(--bg-primary);
|
|
333
|
+
border: 3px solid var(--accent-primary);
|
|
334
|
+
border-radius: 8px;
|
|
335
|
+
cursor: pointer;
|
|
336
|
+
transition: all 0.2s;
|
|
337
|
+
display: flex;
|
|
338
|
+
flex-direction: column;
|
|
339
|
+
align-items: center;
|
|
340
|
+
gap: 8px;
|
|
341
|
+
">
|
|
342
|
+
<span style="font-size: 2em;">💻</span>
|
|
343
|
+
<span style="font-weight: bold; color: var(--text-primary);">System</span>
|
|
344
|
+
</button>
|
|
345
|
+
</div>
|
|
346
|
+
<p style="color: var(--text-secondary); font-size: 0.9em; margin: 0;">
|
|
347
|
+
System theme automatically matches your operating system's appearance settings.
|
|
348
|
+
</p>
|
|
349
|
+
</div>
|
|
350
|
+
|
|
351
|
+
<!-- Macros Tab -->
|
|
352
|
+
<div id="macros-tab-content" class="settings-tab-content" style="display: none;">
|
|
353
|
+
<!-- Setting: Show gear buttons on spells -->
|
|
354
|
+
<div style="background: var(--bg-tertiary); padding: 12px; border-radius: 8px; margin-bottom: 15px;">
|
|
355
|
+
<label style="display: flex; align-items: center; gap: 10px; cursor: pointer;">
|
|
356
|
+
<input type="checkbox" id="show-macro-buttons-setting" style="width: 18px; height: 18px;" ${showCustomMacroButtons ? 'checked' : ''}>
|
|
357
|
+
<span style="color: var(--text-primary); font-weight: bold;">Show ⚙️ macro buttons on spells</span>
|
|
358
|
+
</label>
|
|
359
|
+
<p style="margin: 8px 0 0 28px; color: var(--text-secondary); font-size: 0.85em;">
|
|
360
|
+
When enabled, shows a gear button on each spell to configure custom Roll20 macros.
|
|
361
|
+
</p>
|
|
362
|
+
</div>
|
|
363
|
+
|
|
364
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
|
|
365
|
+
<h4 style="margin: 0; color: var(--text-primary);">Your Custom Macros</h4>
|
|
366
|
+
<button id="add-macro-btn-settings" style="
|
|
367
|
+
padding: 8px 16px;
|
|
368
|
+
background: var(--accent-primary);
|
|
369
|
+
color: white;
|
|
370
|
+
border: none;
|
|
371
|
+
border-radius: 6px;
|
|
372
|
+
cursor: pointer;
|
|
373
|
+
font-weight: bold;
|
|
374
|
+
">➕ Add Macro</button>
|
|
375
|
+
</div>
|
|
376
|
+
<div id="custom-macros-container-settings"></div>
|
|
377
|
+
</div>
|
|
378
|
+
|
|
379
|
+
<!-- GM Integration Tab -->
|
|
380
|
+
<div id="gm-tab-content" class="settings-tab-content" style="display: none;">
|
|
381
|
+
<h4 style="margin: 0 0 15px 0; color: var(--text-primary);">👑 GM Integration</h4>
|
|
382
|
+
|
|
383
|
+
<!-- DiceCloud Sync Section -->
|
|
384
|
+
<div id="dicecloud-sync-section" style="background: var(--bg-tertiary); padding: 16px; border-radius: 8px; margin-bottom: 20px; display: none;">
|
|
385
|
+
<h5 style="margin: 0 0 10px 0; color: var(--text-primary);">🔄 DiceCloud Sync</h5>
|
|
386
|
+
<p style="margin: 0 0 15px 0; color: var(--text-secondary); font-size: 0.9em;">
|
|
387
|
+
Manually sync all character data to DiceCloud. This updates HP, spell slots, Channel Divinity, class resources, and all other tracked values.
|
|
388
|
+
</p>
|
|
389
|
+
<button id="manual-sync-btn" style="
|
|
390
|
+
background: linear-gradient(135deg, #3498db 0%, #2980b9 100%);
|
|
391
|
+
color: white;
|
|
392
|
+
border: none;
|
|
393
|
+
border-radius: 8px;
|
|
394
|
+
padding: 12px 24px;
|
|
395
|
+
font-size: 1em;
|
|
396
|
+
font-weight: bold;
|
|
397
|
+
cursor: pointer;
|
|
398
|
+
transition: all 0.3s ease;
|
|
399
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
|
400
|
+
width: 100%;
|
|
401
|
+
">
|
|
402
|
+
🔄 Sync to DiceCloud Now
|
|
403
|
+
</button>
|
|
404
|
+
<div id="sync-status" style="margin-top: 10px; padding: 8px; border-radius: 6px; font-size: 0.9em; display: none;"></div>
|
|
405
|
+
</div>
|
|
406
|
+
|
|
407
|
+
<div style="background: var(--bg-tertiary); padding: 16px; border-radius: 8px; margin-bottom: 20px;">
|
|
408
|
+
<h5 style="margin: 0 0 10px 0; color: var(--text-primary);">Share Character with GM</h5>
|
|
409
|
+
<p style="margin: 0 0 15px 0; color: var(--text-secondary); font-size: 0.9em;">
|
|
410
|
+
Share your complete character sheet with the Game Master. This sends all your character data including abilities, skills, actions, spells, and equipment.
|
|
411
|
+
</p>
|
|
412
|
+
<button id="show-to-gm-btn" class="show-to-gm-btn" style="
|
|
413
|
+
background: linear-gradient(135deg, #f39c12 0%, #e67e22 100%);
|
|
414
|
+
color: white;
|
|
415
|
+
border: none;
|
|
416
|
+
border-radius: 8px;
|
|
417
|
+
padding: 12px 24px;
|
|
418
|
+
font-size: 1em;
|
|
419
|
+
font-weight: bold;
|
|
420
|
+
cursor: pointer;
|
|
421
|
+
transition: all 0.3s ease;
|
|
422
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
|
423
|
+
width: 100%;
|
|
424
|
+
">
|
|
425
|
+
👑 Share Character with GM
|
|
426
|
+
</button>
|
|
427
|
+
</div>
|
|
428
|
+
|
|
429
|
+
<div style="background: var(--bg-tertiary); padding: 16px; border-radius: 8px; margin-bottom: 20px;">
|
|
430
|
+
<h5 style="margin: 0 0 10px 0; color: var(--text-primary);">How It Works</h5>
|
|
431
|
+
<ul style="margin: 0; padding-left: 20px; color: var(--text-secondary); font-size: 0.9em;">
|
|
432
|
+
<li>Click the button above to share your character</li>
|
|
433
|
+
<li>Your character data appears in the Roll20 chat</li>
|
|
434
|
+
<li>The GM receives your complete character sheet</li>
|
|
435
|
+
<li>GM can view your stats, actions, and spells</li>
|
|
436
|
+
<li>Helps GM track party composition and abilities</li>
|
|
437
|
+
</ul>
|
|
438
|
+
</div>
|
|
439
|
+
|
|
440
|
+
<div style="background: var(--bg-tertiary); padding: 16px; border-radius: 8px;">
|
|
441
|
+
<h5 style="margin: 0 0 10px 0; color: var(--text-primary);">Privacy Note</h5>
|
|
442
|
+
<p style="margin: 0; color: var(--text-secondary); font-size: 0.9em;">
|
|
443
|
+
Only share character data when requested by your GM. The shared data includes your complete character sheet for game management purposes.
|
|
444
|
+
</p>
|
|
445
|
+
</div>
|
|
446
|
+
</div>
|
|
447
|
+
</div>
|
|
448
|
+
</div>
|
|
449
|
+
`;
|
|
450
|
+
|
|
451
|
+
document.body.appendChild(modal);
|
|
452
|
+
|
|
453
|
+
// Set up tab switching
|
|
454
|
+
modal.querySelectorAll('.settings-tab').forEach(tab => {
|
|
455
|
+
tab.addEventListener('click', () => {
|
|
456
|
+
const tabName = tab.dataset.tab;
|
|
457
|
+
|
|
458
|
+
// Update tab buttons
|
|
459
|
+
modal.querySelectorAll('.settings-tab').forEach(t => {
|
|
460
|
+
t.classList.remove('active');
|
|
461
|
+
t.style.color = 'var(--text-secondary)';
|
|
462
|
+
t.style.background = 'transparent';
|
|
463
|
+
});
|
|
464
|
+
tab.classList.add('active');
|
|
465
|
+
tab.style.color = 'var(--text-primary)';
|
|
466
|
+
tab.style.background = 'var(--bg-secondary)';
|
|
467
|
+
|
|
468
|
+
// Update content
|
|
469
|
+
modal.querySelectorAll('.settings-tab-content').forEach(content => {
|
|
470
|
+
content.style.display = 'none';
|
|
471
|
+
});
|
|
472
|
+
modal.querySelector(`#${tabName}-tab-content`).style.display = 'block';
|
|
473
|
+
});
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
// Set up theme options
|
|
477
|
+
const currentTheme = typeof ThemeManager !== 'undefined' ? ThemeManager.getCurrentTheme() : 'light';
|
|
478
|
+
modal.querySelectorAll('.theme-option').forEach(option => {
|
|
479
|
+
if (option.dataset.theme === currentTheme) {
|
|
480
|
+
option.style.borderColor = 'var(--accent-primary)';
|
|
481
|
+
option.classList.add('active');
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
option.addEventListener('click', () => {
|
|
485
|
+
const theme = option.dataset.theme;
|
|
486
|
+
if (typeof ThemeManager !== 'undefined') {
|
|
487
|
+
ThemeManager.setTheme(theme);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Update active state
|
|
491
|
+
modal.querySelectorAll('.theme-option').forEach(opt => {
|
|
492
|
+
opt.style.borderColor = 'var(--border-color)';
|
|
493
|
+
opt.classList.remove('active');
|
|
494
|
+
});
|
|
495
|
+
option.style.borderColor = 'var(--accent-primary)';
|
|
496
|
+
option.classList.add('active');
|
|
497
|
+
});
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
// Set up GM share button
|
|
501
|
+
const showToGMBtn = modal.querySelector('#show-to-gm-btn');
|
|
502
|
+
if (showToGMBtn) {
|
|
503
|
+
showToGMBtn.addEventListener('click', () => {
|
|
504
|
+
// Call the global shareCharacterWithGM function if it exists
|
|
505
|
+
if (typeof shareCharacterWithGM === 'function') {
|
|
506
|
+
shareCharacterWithGM();
|
|
507
|
+
// Close the settings modal after sharing
|
|
508
|
+
document.body.removeChild(modal);
|
|
509
|
+
} else {
|
|
510
|
+
debug.error('❌ shareCharacterWithGM function not available');
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Load macros into settings
|
|
516
|
+
updateMacrosDisplayInSettings();
|
|
517
|
+
|
|
518
|
+
// Initialize Show to GM button in settings
|
|
519
|
+
initShowToGM();
|
|
520
|
+
|
|
521
|
+
// Initialize manual DiceCloud sync button (experimental builds only)
|
|
522
|
+
initManualSyncButton();
|
|
523
|
+
|
|
524
|
+
// Set up add macro button
|
|
525
|
+
modal.querySelector('#add-macro-btn-settings').addEventListener('click', () => {
|
|
526
|
+
showAddMacroModal();
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
// Set up show macro buttons checkbox
|
|
530
|
+
const macroButtonsCheckbox = modal.querySelector('#show-macro-buttons-setting');
|
|
531
|
+
if (macroButtonsCheckbox) {
|
|
532
|
+
macroButtonsCheckbox.addEventListener('change', (e) => {
|
|
533
|
+
showCustomMacroButtons = e.target.checked;
|
|
534
|
+
localStorage.setItem('showCustomMacroButtons', showCustomMacroButtons ? 'true' : 'false');
|
|
535
|
+
debug.log(`⚙️ Custom macro buttons ${showCustomMacroButtons ? 'enabled' : 'disabled'}`);
|
|
536
|
+
// Rebuild spells to show/hide gear buttons
|
|
537
|
+
rebuildSpells();
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Close button
|
|
542
|
+
modal.querySelector('#settings-close-btn').addEventListener('click', () => {
|
|
543
|
+
document.body.removeChild(modal);
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
// Close on background click
|
|
547
|
+
modal.addEventListener('click', (e) => {
|
|
548
|
+
if (e.target === modal) {
|
|
549
|
+
document.body.removeChild(modal);
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Show add macro modal (simplified version for settings)
|
|
556
|
+
*/
|
|
557
|
+
function showAddMacroModal() {
|
|
558
|
+
const modal = document.createElement('div');
|
|
559
|
+
modal.id = 'add-macro-modal';
|
|
560
|
+
modal.style.cssText = `
|
|
561
|
+
position: fixed;
|
|
562
|
+
top: 0;
|
|
563
|
+
left: 0;
|
|
564
|
+
width: 100%;
|
|
565
|
+
height: 100%;
|
|
566
|
+
background: rgba(0, 0, 0, 0.8);
|
|
567
|
+
display: flex;
|
|
568
|
+
align-items: center;
|
|
569
|
+
justify-content: center;
|
|
570
|
+
z-index: 10001;
|
|
571
|
+
`;
|
|
572
|
+
|
|
573
|
+
modal.innerHTML = `
|
|
574
|
+
<div style="
|
|
575
|
+
background: var(--bg-secondary);
|
|
576
|
+
border: 2px solid var(--border-color);
|
|
577
|
+
border-radius: 12px;
|
|
578
|
+
padding: 24px;
|
|
579
|
+
max-width: 500px;
|
|
580
|
+
width: 90%;
|
|
581
|
+
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
|
|
582
|
+
">
|
|
583
|
+
<h3 style="margin: 0 0 20px 0; color: var(--text-primary);">🎲 Add Custom Macro</h3>
|
|
584
|
+
|
|
585
|
+
<div style="margin-bottom: 16px;">
|
|
586
|
+
<label style="display: block; margin-bottom: 6px; font-weight: bold; color: var(--text-primary);">
|
|
587
|
+
Macro Name
|
|
588
|
+
</label>
|
|
589
|
+
<input type="text" id="macro-name-input" placeholder="e.g., Sneak Attack" style="
|
|
590
|
+
width: 100%;
|
|
591
|
+
padding: 10px;
|
|
592
|
+
border: 2px solid var(--border-color);
|
|
593
|
+
border-radius: 6px;
|
|
594
|
+
font-size: 14px;
|
|
595
|
+
background: var(--bg-primary);
|
|
596
|
+
color: var(--text-primary);
|
|
597
|
+
">
|
|
598
|
+
</div>
|
|
599
|
+
|
|
600
|
+
<div style="margin-bottom: 16px;">
|
|
601
|
+
<label style="display: block; margin-bottom: 6px; font-weight: bold; color: var(--text-primary);">
|
|
602
|
+
Roll Formula
|
|
603
|
+
</label>
|
|
604
|
+
<input type="text" id="macro-formula-input" placeholder="e.g., 3d6" style="
|
|
605
|
+
width: 100%;
|
|
606
|
+
padding: 10px;
|
|
607
|
+
border: 2px solid var(--border-color);
|
|
608
|
+
border-radius: 6px;
|
|
609
|
+
font-size: 14px;
|
|
610
|
+
font-family: monospace;
|
|
611
|
+
background: var(--bg-primary);
|
|
612
|
+
color: var(--text-primary);
|
|
613
|
+
">
|
|
614
|
+
<small style="color: var(--text-secondary); font-size: 0.85em;">
|
|
615
|
+
Examples: 1d20+5, 2d6+3, 8d6, 1d20+dexterity.modifier
|
|
616
|
+
</small>
|
|
617
|
+
</div>
|
|
618
|
+
|
|
619
|
+
<div style="margin-bottom: 20px;">
|
|
620
|
+
<label style="display: block; margin-bottom: 6px; font-weight: bold; color: var(--text-primary);">
|
|
621
|
+
Description (optional)
|
|
622
|
+
</label>
|
|
623
|
+
<input type="text" id="macro-description-input" placeholder="e.g., Extra damage on hit" style="
|
|
624
|
+
width: 100%;
|
|
625
|
+
padding: 10px;
|
|
626
|
+
border: 2px solid var(--border-color);
|
|
627
|
+
border-radius: 6px;
|
|
628
|
+
font-size: 14px;
|
|
629
|
+
background: var(--bg-primary);
|
|
630
|
+
color: var(--text-primary);
|
|
631
|
+
">
|
|
632
|
+
</div>
|
|
633
|
+
|
|
634
|
+
<div style="display: flex; gap: 10px; justify-content: flex-end;">
|
|
635
|
+
<button id="macro-cancel-btn" style="
|
|
636
|
+
padding: 10px 20px;
|
|
637
|
+
background: var(--bg-tertiary);
|
|
638
|
+
color: var(--text-primary);
|
|
639
|
+
border: 2px solid var(--border-color);
|
|
640
|
+
border-radius: 6px;
|
|
641
|
+
cursor: pointer;
|
|
642
|
+
font-weight: bold;
|
|
643
|
+
">
|
|
644
|
+
Cancel
|
|
645
|
+
</button>
|
|
646
|
+
<button id="macro-save-btn" style="
|
|
647
|
+
padding: 10px 20px;
|
|
648
|
+
background: var(--accent-primary);
|
|
649
|
+
color: white;
|
|
650
|
+
border: none;
|
|
651
|
+
border-radius: 6px;
|
|
652
|
+
cursor: pointer;
|
|
653
|
+
font-weight: bold;
|
|
654
|
+
">
|
|
655
|
+
Save Macro
|
|
656
|
+
</button>
|
|
657
|
+
</div>
|
|
658
|
+
</div>
|
|
659
|
+
`;
|
|
660
|
+
|
|
661
|
+
document.body.appendChild(modal);
|
|
662
|
+
|
|
663
|
+
document.getElementById('macro-name-input').focus();
|
|
664
|
+
|
|
665
|
+
document.getElementById('macro-cancel-btn').addEventListener('click', () => {
|
|
666
|
+
document.body.removeChild(modal);
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
document.getElementById('macro-save-btn').addEventListener('click', () => {
|
|
670
|
+
const name = document.getElementById('macro-name-input').value.trim();
|
|
671
|
+
const formula = document.getElementById('macro-formula-input').value.trim();
|
|
672
|
+
const description = document.getElementById('macro-description-input').value.trim();
|
|
673
|
+
|
|
674
|
+
if (!name || !formula) {
|
|
675
|
+
alert('Please enter both a name and formula for the macro.');
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
addCustomMacro(name, formula, description);
|
|
680
|
+
document.body.removeChild(modal);
|
|
681
|
+
updateMacrosDisplayInSettings();
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
modal.addEventListener('click', (e) => {
|
|
685
|
+
if (e.target === modal) {
|
|
686
|
+
document.body.removeChild(modal);
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* Update macros display in settings modal
|
|
693
|
+
*/
|
|
694
|
+
function updateMacrosDisplayInSettings() {
|
|
695
|
+
const container = document.getElementById('custom-macros-container-settings');
|
|
696
|
+
if (!container) return;
|
|
697
|
+
|
|
698
|
+
if (customMacros.length === 0) {
|
|
699
|
+
container.innerHTML = '<p style="text-align: center; color: #888; padding: 15px;">No custom macros yet. Click "Add Macro" to create one.</p>';
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
container.innerHTML = customMacros.map(macro => `
|
|
704
|
+
<div class="macro-item" style="
|
|
705
|
+
background: var(--bg-primary);
|
|
706
|
+
border: 2px solid var(--border-color);
|
|
707
|
+
border-radius: 8px;
|
|
708
|
+
padding: 12px;
|
|
709
|
+
margin-bottom: 10px;
|
|
710
|
+
display: flex;
|
|
711
|
+
justify-content: space-between;
|
|
712
|
+
align-items: center;
|
|
713
|
+
transition: all 0.2s;
|
|
714
|
+
">
|
|
715
|
+
<div style="flex: 1;">
|
|
716
|
+
<div style="font-weight: bold; color: var(--text-primary); margin-bottom: 4px;">
|
|
717
|
+
${macro.name}
|
|
718
|
+
</div>
|
|
719
|
+
<div style="font-family: monospace; color: var(--accent-info); font-size: 0.9em; margin-bottom: 4px;">
|
|
720
|
+
${macro.formula}
|
|
721
|
+
</div>
|
|
722
|
+
${macro.description ? `<div style="font-size: 0.85em; color: var(--text-secondary);">${macro.description}</div>` : ''}
|
|
723
|
+
</div>
|
|
724
|
+
<div style="display: flex; gap: 8px;">
|
|
725
|
+
<button class="macro-roll-btn" data-macro-id="${macro.id}" style="
|
|
726
|
+
background: var(--accent-primary);
|
|
727
|
+
color: white;
|
|
728
|
+
border: none;
|
|
729
|
+
padding: 8px 16px;
|
|
730
|
+
border-radius: 6px;
|
|
731
|
+
cursor: pointer;
|
|
732
|
+
font-weight: bold;
|
|
733
|
+
transition: all 0.2s;
|
|
734
|
+
">
|
|
735
|
+
🎲 Roll
|
|
736
|
+
</button>
|
|
737
|
+
<button class="macro-delete-btn" data-macro-id="${macro.id}" style="
|
|
738
|
+
background: var(--accent-danger);
|
|
739
|
+
color: white;
|
|
740
|
+
border: none;
|
|
741
|
+
padding: 8px 12px;
|
|
742
|
+
border-radius: 6px;
|
|
743
|
+
cursor: pointer;
|
|
744
|
+
transition: all 0.2s;
|
|
745
|
+
">
|
|
746
|
+
🗑️
|
|
747
|
+
</button>
|
|
748
|
+
</div>
|
|
749
|
+
</div>
|
|
750
|
+
`).join('');
|
|
751
|
+
|
|
752
|
+
container.querySelectorAll('.macro-roll-btn').forEach(btn => {
|
|
753
|
+
btn.addEventListener('click', () => {
|
|
754
|
+
const macroId = btn.dataset.macroId;
|
|
755
|
+
const macro = customMacros.find(m => m.id === macroId);
|
|
756
|
+
if (macro) {
|
|
757
|
+
executeMacro(macro);
|
|
758
|
+
// Close settings modal after rolling
|
|
759
|
+
const settingsModal = document.getElementById('settings-modal');
|
|
760
|
+
if (settingsModal) {
|
|
761
|
+
document.body.removeChild(settingsModal);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
});
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
container.querySelectorAll('.macro-delete-btn').forEach(btn => {
|
|
768
|
+
btn.addEventListener('click', () => {
|
|
769
|
+
const macroId = btn.dataset.macroId;
|
|
770
|
+
const macro = customMacros.find(m => m.id === macroId);
|
|
771
|
+
if (macro && confirm(`Delete macro "${macro.name}"?`)) {
|
|
772
|
+
deleteCustomMacro(macroId);
|
|
773
|
+
updateMacrosDisplayInSettings();
|
|
774
|
+
}
|
|
775
|
+
});
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* Initialize custom macros system
|
|
781
|
+
*/
|
|
782
|
+
function initCustomMacros() {
|
|
783
|
+
loadCustomMacros();
|
|
784
|
+
|
|
785
|
+
// Load custom macro button setting (default: disabled)
|
|
786
|
+
const savedSetting = localStorage.getItem('showCustomMacroButtons');
|
|
787
|
+
showCustomMacroButtons = savedSetting === 'true';
|
|
788
|
+
debug.log(`⚙️ Custom macro buttons setting: ${showCustomMacroButtons ? 'enabled' : 'disabled'}`);
|
|
789
|
+
|
|
790
|
+
debug.log('🎲 Custom macros system initialized');
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* Initialize settings button
|
|
795
|
+
*/
|
|
796
|
+
function initSettingsButton() {
|
|
797
|
+
const settingsBtn = document.getElementById('settings-btn');
|
|
798
|
+
if (settingsBtn) {
|
|
799
|
+
settingsBtn.addEventListener('click', showSettingsModal);
|
|
800
|
+
debug.log('⚙️ Settings button initialized');
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// ===== EXPORTS =====
|
|
805
|
+
|
|
806
|
+
// Export functions to globalThis
|
|
807
|
+
globalThis.loadCustomMacros = loadCustomMacros;
|
|
808
|
+
globalThis.saveAllCustomMacros = saveAllCustomMacros;
|
|
809
|
+
globalThis.addCustomMacro = addCustomMacro;
|
|
810
|
+
globalThis.deleteCustomMacro = deleteCustomMacro;
|
|
811
|
+
globalThis.executeMacro = executeMacro;
|
|
812
|
+
globalThis.updateMacrosDisplay = updateMacrosDisplay;
|
|
813
|
+
globalThis.showSettingsModal = showSettingsModal;
|
|
814
|
+
globalThis.showAddMacroModal = showAddMacroModal;
|
|
815
|
+
globalThis.updateMacrosDisplayInSettings = updateMacrosDisplayInSettings;
|
|
816
|
+
globalThis.initCustomMacros = initCustomMacros;
|
|
817
|
+
globalThis.initSettingsButton = initSettingsButton;
|
|
818
|
+
|
|
819
|
+
// Export state variables to globalThis
|
|
820
|
+
globalThis.customMacros = customMacros;
|
|
821
|
+
globalThis.showCustomMacroButtons = showCustomMacroButtons;
|
|
822
|
+
|
|
823
|
+
debug.log('✅ Macro System module loaded');
|
|
824
|
+
|
|
825
|
+
})();
|