@kenjura/ursa 0.75.0 → 0.77.0
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/CHANGELOG.md +85 -0
- package/meta/default.css +149 -3
- package/meta/templates/default-template/default.css +1268 -0
- package/meta/{default-template.html → templates/default-template/index.html} +49 -2
- package/meta/{menu.js → templates/default-template/menu.js} +1 -1
- package/meta/templates/default-template/sectionify.js +46 -0
- package/meta/templates/default-template/widgets.js +701 -0
- package/package.json +1 -1
- package/src/dev.js +125 -34
- package/src/helper/assetBundler.js +471 -0
- package/src/helper/build/autoIndex.js +26 -23
- package/src/helper/build/cacheBust.js +79 -0
- package/src/helper/build/navCache.js +4 -0
- package/src/helper/build/templates.js +176 -19
- package/src/helper/build/watchCache.js +7 -0
- package/src/helper/customMenu.js +4 -2
- package/src/helper/dependencyTracker.js +269 -0
- package/src/helper/findScriptJs.js +29 -0
- package/src/helper/findStyleCss.js +29 -0
- package/src/helper/portUtils.js +132 -0
- package/src/jobs/generate.js +276 -59
- package/src/serve.js +446 -162
- package/meta/character-sheet.css +0 -50
- package/meta/widgets.js +0 -376
- /package/meta/{goudy_bookletter_1911-webfont.woff → shared/goudy_bookletter_1911-webfont.woff} +0 -0
- /package/meta/{character-sheet/css → templates/character-sheet-template}/character-sheet.css +0 -0
- /package/meta/{character-sheet/js → templates/character-sheet-template}/components.js +0 -0
- /package/meta/{cssui.bundle.min.css → templates/character-sheet-template/cssui.bundle.min.css} +0 -0
- /package/meta/{character-sheet-template.html → templates/character-sheet-template/index.html} +0 -0
- /package/meta/{character-sheet/js → templates/character-sheet-template}/main.js +0 -0
- /package/meta/{character-sheet/js → templates/character-sheet-template}/model.js +0 -0
- /package/meta/{search.js → templates/default-template/search.js} +0 -0
- /package/meta/{sticky.js → templates/default-template/sticky.js} +0 -0
- /package/meta/{toc-generator.js → templates/default-template/toc-generator.js} +0 -0
- /package/meta/{toc.js → templates/default-template/toc.js} +0 -0
- /package/meta/{template2.html → templates/template2/index.html} +0 -0
package/meta/character-sheet.css
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
nav#tabs {
|
|
2
|
-
position: absolute;
|
|
3
|
-
top: 0;
|
|
4
|
-
left: calc(50% - 20em);
|
|
5
|
-
background: rgba(255,255,255,0.1);
|
|
6
|
-
height: 40px;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
nav#tabs li {
|
|
10
|
-
display: inline-block;
|
|
11
|
-
}
|
|
12
|
-
nav#tabs li a {
|
|
13
|
-
background: rgba(255,255,255,0.1);
|
|
14
|
-
height: 40px;
|
|
15
|
-
line-height: 40px;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
article {
|
|
19
|
-
box-sizing: content-box;
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
div#tab-container {
|
|
24
|
-
--tab-panel-background: transparent;
|
|
25
|
-
--tab-background-color: rgba(255,255,255,0.25) !important;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
[data-tab=tab1]:checked~nav [data-tab-label=tab1], [data-tab=tab2]:checked~nav [data-tab-label=tab2], [data-tab=tab3]:checked~nav [data-tab-label=tab3] {
|
|
29
|
-
--tab-background-color: rgba(255,255,255,0.55) !important;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
#character-sheet .rollable {
|
|
33
|
-
display: inline-block;
|
|
34
|
-
position: relative;
|
|
35
|
-
left: 3px;
|
|
36
|
-
transition: filter 0.3s, transform 0.3s;
|
|
37
|
-
|
|
38
|
-
animation-play-state: paused;
|
|
39
|
-
}
|
|
40
|
-
#character-sheet .rollable:hover {
|
|
41
|
-
filter: brightness(1.5);
|
|
42
|
-
transform: scale(1.1);
|
|
43
|
-
animation-play-state: running;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
#character-sheet .rollable .hexagon {
|
|
47
|
-
position: absolute;
|
|
48
|
-
top: 0px;
|
|
49
|
-
left: -7.5px;
|
|
50
|
-
}
|
package/meta/widgets.js
DELETED
|
@@ -1,376 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Widget system for the top nav right-side panel.
|
|
3
|
-
*
|
|
4
|
-
* Widgets appear as icon buttons in the nav bar. Clicking a button toggles a
|
|
5
|
-
* dropdown panel anchored to the right side below the nav. Only one widget can
|
|
6
|
-
* be open at a time.
|
|
7
|
-
*
|
|
8
|
-
* Built-in widgets: TOC, Search, Profile
|
|
9
|
-
*/
|
|
10
|
-
class WidgetManager {
|
|
11
|
-
constructor() {
|
|
12
|
-
this.dropdown = document.getElementById('widget-dropdown');
|
|
13
|
-
this.buttons = document.querySelectorAll('.widget-button[data-widget]');
|
|
14
|
-
this.activeWidget = null;
|
|
15
|
-
|
|
16
|
-
if (!this.dropdown || this.buttons.length === 0) return;
|
|
17
|
-
|
|
18
|
-
this.init();
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
init() {
|
|
22
|
-
// Bind button clicks
|
|
23
|
-
this.buttons.forEach(btn => {
|
|
24
|
-
btn.addEventListener('click', (e) => {
|
|
25
|
-
e.stopPropagation();
|
|
26
|
-
const widgetName = btn.dataset.widget;
|
|
27
|
-
this.toggle(widgetName);
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
// Close on outside click
|
|
32
|
-
document.addEventListener('click', (e) => {
|
|
33
|
-
if (this.activeWidget &&
|
|
34
|
-
!this.dropdown.contains(e.target) &&
|
|
35
|
-
!e.target.closest('.widget-button')) {
|
|
36
|
-
this.close();
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
// Close on Escape
|
|
41
|
-
document.addEventListener('keydown', (e) => {
|
|
42
|
-
if (e.key === 'Escape' && this.activeWidget) {
|
|
43
|
-
this.close();
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
// Initialize search widget content
|
|
48
|
-
this.initSearchWidget();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Toggle a widget open/closed. If a different widget is open, switch to the new one.
|
|
53
|
-
*/
|
|
54
|
-
toggle(widgetName) {
|
|
55
|
-
if (this.activeWidget === widgetName) {
|
|
56
|
-
this.close();
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
this.open(widgetName);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Open a specific widget panel.
|
|
65
|
-
*/
|
|
66
|
-
open(widgetName) {
|
|
67
|
-
// Close any open widget first
|
|
68
|
-
if (this.activeWidget) {
|
|
69
|
-
this.deactivateContent(this.activeWidget);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
this.activeWidget = widgetName;
|
|
73
|
-
|
|
74
|
-
// Show dropdown
|
|
75
|
-
this.dropdown.classList.remove('hidden');
|
|
76
|
-
this.dropdown.dataset.activeWidget = widgetName;
|
|
77
|
-
|
|
78
|
-
// Show the correct content panel
|
|
79
|
-
this.activateContent(widgetName);
|
|
80
|
-
|
|
81
|
-
// Update button states
|
|
82
|
-
this.buttons.forEach(btn => {
|
|
83
|
-
btn.classList.toggle('active', btn.dataset.widget === widgetName);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
// Fire event for other scripts to listen to
|
|
87
|
-
document.dispatchEvent(new CustomEvent('widget-opened', { detail: { widget: widgetName } }));
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Close the currently open widget.
|
|
92
|
-
*/
|
|
93
|
-
close() {
|
|
94
|
-
if (!this.activeWidget) return;
|
|
95
|
-
|
|
96
|
-
const closing = this.activeWidget;
|
|
97
|
-
this.deactivateContent(closing);
|
|
98
|
-
|
|
99
|
-
this.activeWidget = null;
|
|
100
|
-
this.dropdown.classList.add('hidden');
|
|
101
|
-
delete this.dropdown.dataset.activeWidget;
|
|
102
|
-
|
|
103
|
-
// Update button states
|
|
104
|
-
this.buttons.forEach(btn => btn.classList.remove('active'));
|
|
105
|
-
|
|
106
|
-
// Fire event
|
|
107
|
-
document.dispatchEvent(new CustomEvent('widget-closed', { detail: { widget: closing } }));
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Show a widget's content panel.
|
|
112
|
-
*/
|
|
113
|
-
activateContent(widgetName) {
|
|
114
|
-
const content = this.dropdown.querySelector(`.widget-content[data-widget="${widgetName}"]`);
|
|
115
|
-
if (content) {
|
|
116
|
-
content.classList.add('active');
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Widget-specific activation
|
|
120
|
-
if (widgetName === 'search') {
|
|
121
|
-
this.activateSearch();
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Hide a widget's content panel.
|
|
127
|
-
*/
|
|
128
|
-
deactivateContent(widgetName) {
|
|
129
|
-
const content = this.dropdown.querySelector(`.widget-content[data-widget="${widgetName}"]`);
|
|
130
|
-
if (content) {
|
|
131
|
-
content.classList.remove('active');
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Widget-specific deactivation
|
|
135
|
-
if (widgetName === 'search') {
|
|
136
|
-
this.deactivateSearch();
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Initialize search widget — move the search input and results into the widget panel.
|
|
142
|
-
*/
|
|
143
|
-
initSearchWidget() {
|
|
144
|
-
const searchContent = document.getElementById('widget-content-search');
|
|
145
|
-
if (!searchContent) return;
|
|
146
|
-
|
|
147
|
-
// The search input and wrapper are created by search.js (GlobalSearch).
|
|
148
|
-
// We need to wait for it to be ready, then move elements into the widget.
|
|
149
|
-
// Use a short delay to let GlobalSearch initialize first.
|
|
150
|
-
const moveSearch = () => {
|
|
151
|
-
const searchWrapper = document.querySelector('.search-wrapper-inline');
|
|
152
|
-
const searchResults = document.getElementById('search-results');
|
|
153
|
-
|
|
154
|
-
if (searchWrapper) {
|
|
155
|
-
// Clone the search input into the widget (the inline one stays for non-top-menu/mobile)
|
|
156
|
-
// Actually, we'll relocate the existing elements when the widget is activated.
|
|
157
|
-
// For now, create a dedicated search input for the widget.
|
|
158
|
-
const widgetInput = document.createElement('input');
|
|
159
|
-
widgetInput.id = 'widget-search-input';
|
|
160
|
-
widgetInput.type = 'text';
|
|
161
|
-
widgetInput.placeholder = 'Search...';
|
|
162
|
-
widgetInput.className = 'widget-search-input';
|
|
163
|
-
|
|
164
|
-
const widgetWrapper = document.createElement('div');
|
|
165
|
-
widgetWrapper.className = 'widget-search-wrapper';
|
|
166
|
-
widgetWrapper.appendChild(widgetInput);
|
|
167
|
-
|
|
168
|
-
// Create dedicated results container for widget
|
|
169
|
-
const widgetResults = document.createElement('div');
|
|
170
|
-
widgetResults.id = 'widget-search-results';
|
|
171
|
-
widgetResults.className = 'widget-search-results';
|
|
172
|
-
|
|
173
|
-
searchContent.appendChild(widgetWrapper);
|
|
174
|
-
searchContent.appendChild(widgetResults);
|
|
175
|
-
|
|
176
|
-
// Bind the widget search input to the GlobalSearch instance
|
|
177
|
-
this.bindWidgetSearch(widgetInput, widgetResults);
|
|
178
|
-
}
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
// Wait for search.js to initialize
|
|
182
|
-
setTimeout(moveSearch, 50);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Bind the widget search input to use GlobalSearch's search functionality.
|
|
187
|
-
*/
|
|
188
|
-
bindWidgetSearch(input, resultsContainer) {
|
|
189
|
-
this._widgetSearchInput = input;
|
|
190
|
-
this._widgetSearchResults = resultsContainer;
|
|
191
|
-
|
|
192
|
-
let currentSelection = -1;
|
|
193
|
-
|
|
194
|
-
input.addEventListener('input', () => {
|
|
195
|
-
const query = input.value.trim();
|
|
196
|
-
this.performWidgetSearch(query);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
input.addEventListener('keydown', (e) => {
|
|
200
|
-
const items = resultsContainer.querySelectorAll('.search-result-item');
|
|
201
|
-
|
|
202
|
-
switch (e.key) {
|
|
203
|
-
case 'ArrowDown':
|
|
204
|
-
e.preventDefault();
|
|
205
|
-
if (items.length > 0) {
|
|
206
|
-
currentSelection = Math.min(currentSelection + 1, items.length - 1);
|
|
207
|
-
this.updateWidgetSearchSelection(items, currentSelection);
|
|
208
|
-
}
|
|
209
|
-
break;
|
|
210
|
-
case 'ArrowUp':
|
|
211
|
-
e.preventDefault();
|
|
212
|
-
if (items.length > 0) {
|
|
213
|
-
currentSelection = Math.max(currentSelection - 1, 0);
|
|
214
|
-
this.updateWidgetSearchSelection(items, currentSelection);
|
|
215
|
-
}
|
|
216
|
-
break;
|
|
217
|
-
case 'Enter':
|
|
218
|
-
e.preventDefault();
|
|
219
|
-
if (currentSelection >= 0 && items[currentSelection]) {
|
|
220
|
-
items[currentSelection].click();
|
|
221
|
-
}
|
|
222
|
-
break;
|
|
223
|
-
case 'Escape':
|
|
224
|
-
this.close();
|
|
225
|
-
break;
|
|
226
|
-
}
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
// Reset selection on new search
|
|
230
|
-
input.addEventListener('input', () => { currentSelection = -1; });
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Perform search using GlobalSearch's search logic, rendering into widget results.
|
|
235
|
-
*/
|
|
236
|
-
performWidgetSearch(query) {
|
|
237
|
-
const gs = window.globalSearch;
|
|
238
|
-
const container = this._widgetSearchResults;
|
|
239
|
-
if (!gs || !container) return;
|
|
240
|
-
|
|
241
|
-
container.innerHTML = '';
|
|
242
|
-
|
|
243
|
-
if (!query || query.length < gs.MIN_QUERY_LENGTH) {
|
|
244
|
-
if (query && query.length > 0) {
|
|
245
|
-
container.innerHTML = `<div class="search-result-message">Type at least ${gs.MIN_QUERY_LENGTH} characters to search</div>`;
|
|
246
|
-
}
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
if (!gs.indexLoaded) {
|
|
251
|
-
container.innerHTML = '<div class="search-result-message">Loading search index...</div>';
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const pathResults = gs.searchPaths(query);
|
|
256
|
-
const fullTextResults = gs.searchFullText(query);
|
|
257
|
-
|
|
258
|
-
// Deduplicate
|
|
259
|
-
const pathPaths = new Set(pathResults.map(r => r.path));
|
|
260
|
-
const uniqueFullTextResults = fullTextResults.filter(r => !pathPaths.has(r.path));
|
|
261
|
-
|
|
262
|
-
if (pathResults.length === 0 && uniqueFullTextResults.length === 0) {
|
|
263
|
-
container.innerHTML = `<div class="search-result-message">No results for "${query}"</div>`;
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// Path results section
|
|
268
|
-
if (pathResults.length > 0) {
|
|
269
|
-
const section = document.createElement('div');
|
|
270
|
-
section.className = 'search-section';
|
|
271
|
-
const header = document.createElement('div');
|
|
272
|
-
header.className = 'search-section-header';
|
|
273
|
-
header.textContent = `Title/Path Matches (${pathResults.length})`;
|
|
274
|
-
section.appendChild(header);
|
|
275
|
-
|
|
276
|
-
const limit = Math.min(pathResults.length, 10);
|
|
277
|
-
for (let i = 0; i < limit; i++) {
|
|
278
|
-
section.appendChild(this.createWidgetResultItem(pathResults[i]));
|
|
279
|
-
}
|
|
280
|
-
if (pathResults.length > 10) {
|
|
281
|
-
const more = document.createElement('div');
|
|
282
|
-
more.className = 'search-result-message';
|
|
283
|
-
more.textContent = `... and ${pathResults.length - 10} more`;
|
|
284
|
-
section.appendChild(more);
|
|
285
|
-
}
|
|
286
|
-
container.appendChild(section);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// Full-text results section
|
|
290
|
-
if (uniqueFullTextResults.length > 0) {
|
|
291
|
-
const section = document.createElement('div');
|
|
292
|
-
section.className = 'search-section';
|
|
293
|
-
const header = document.createElement('div');
|
|
294
|
-
header.className = 'search-section-header';
|
|
295
|
-
header.textContent = `Content Matches (${uniqueFullTextResults.length})`;
|
|
296
|
-
section.appendChild(header);
|
|
297
|
-
|
|
298
|
-
const limit = Math.min(uniqueFullTextResults.length, 10);
|
|
299
|
-
for (let i = 0; i < limit; i++) {
|
|
300
|
-
section.appendChild(this.createWidgetResultItem(uniqueFullTextResults[i]));
|
|
301
|
-
}
|
|
302
|
-
if (uniqueFullTextResults.length > 10) {
|
|
303
|
-
const more = document.createElement('div');
|
|
304
|
-
more.className = 'search-result-message';
|
|
305
|
-
more.textContent = `... and ${uniqueFullTextResults.length - 10} more`;
|
|
306
|
-
section.appendChild(more);
|
|
307
|
-
}
|
|
308
|
-
container.appendChild(section);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
* Create a search result item for the widget.
|
|
314
|
-
*/
|
|
315
|
-
createWidgetResultItem(result) {
|
|
316
|
-
const item = document.createElement('div');
|
|
317
|
-
item.className = 'search-result-item';
|
|
318
|
-
|
|
319
|
-
const title = document.createElement('div');
|
|
320
|
-
title.className = 'search-result-title';
|
|
321
|
-
title.textContent = result.title || 'Untitled';
|
|
322
|
-
|
|
323
|
-
const path = document.createElement('div');
|
|
324
|
-
path.className = 'search-result-path';
|
|
325
|
-
path.textContent = result.path || result.url || '';
|
|
326
|
-
|
|
327
|
-
item.appendChild(title);
|
|
328
|
-
item.appendChild(path);
|
|
329
|
-
|
|
330
|
-
item.addEventListener('click', () => {
|
|
331
|
-
window.location.href = result.url || result.path;
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
item.addEventListener('mouseenter', () => {
|
|
335
|
-
// Clear other selections
|
|
336
|
-
item.closest('.widget-search-results')?.querySelectorAll('.search-result-item').forEach(el => {
|
|
337
|
-
el.classList.remove('selected');
|
|
338
|
-
});
|
|
339
|
-
item.classList.add('selected');
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
return item;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
updateWidgetSearchSelection(items, index) {
|
|
346
|
-
items.forEach((item, i) => {
|
|
347
|
-
item.classList.toggle('selected', i === index);
|
|
348
|
-
});
|
|
349
|
-
if (index >= 0 && items[index]) {
|
|
350
|
-
items[index].scrollIntoView({ block: 'nearest' });
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* Called when the search widget is activated.
|
|
356
|
-
*/
|
|
357
|
-
activateSearch() {
|
|
358
|
-
const input = this._widgetSearchInput;
|
|
359
|
-
if (input) {
|
|
360
|
-
// Focus with small delay to allow panel animation
|
|
361
|
-
setTimeout(() => input.focus(), 50);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* Called when the search widget is deactivated.
|
|
367
|
-
*/
|
|
368
|
-
deactivateSearch() {
|
|
369
|
-
// Keep the search query so user can re-open and see results
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// Initialize widgets when DOM is ready
|
|
374
|
-
document.addEventListener('DOMContentLoaded', () => {
|
|
375
|
-
window.widgetManager = new WidgetManager();
|
|
376
|
-
});
|
/package/meta/{goudy_bookletter_1911-webfont.woff → shared/goudy_bookletter_1911-webfont.woff}
RENAMED
|
File without changes
|
/package/meta/{character-sheet/css → templates/character-sheet-template}/character-sheet.css
RENAMED
|
File without changes
|
|
File without changes
|
/package/meta/{cssui.bundle.min.css → templates/character-sheet-template/cssui.bundle.min.css}
RENAMED
|
File without changes
|
/package/meta/{character-sheet-template.html → templates/character-sheet-template/index.html}
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|