@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.
Files changed (36) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/meta/default.css +149 -3
  3. package/meta/templates/default-template/default.css +1268 -0
  4. package/meta/{default-template.html → templates/default-template/index.html} +49 -2
  5. package/meta/{menu.js → templates/default-template/menu.js} +1 -1
  6. package/meta/templates/default-template/sectionify.js +46 -0
  7. package/meta/templates/default-template/widgets.js +701 -0
  8. package/package.json +1 -1
  9. package/src/dev.js +125 -34
  10. package/src/helper/assetBundler.js +471 -0
  11. package/src/helper/build/autoIndex.js +26 -23
  12. package/src/helper/build/cacheBust.js +79 -0
  13. package/src/helper/build/navCache.js +4 -0
  14. package/src/helper/build/templates.js +176 -19
  15. package/src/helper/build/watchCache.js +7 -0
  16. package/src/helper/customMenu.js +4 -2
  17. package/src/helper/dependencyTracker.js +269 -0
  18. package/src/helper/findScriptJs.js +29 -0
  19. package/src/helper/findStyleCss.js +29 -0
  20. package/src/helper/portUtils.js +132 -0
  21. package/src/jobs/generate.js +276 -59
  22. package/src/serve.js +446 -162
  23. package/meta/character-sheet.css +0 -50
  24. package/meta/widgets.js +0 -376
  25. /package/meta/{goudy_bookletter_1911-webfont.woff → shared/goudy_bookletter_1911-webfont.woff} +0 -0
  26. /package/meta/{character-sheet/css → templates/character-sheet-template}/character-sheet.css +0 -0
  27. /package/meta/{character-sheet/js → templates/character-sheet-template}/components.js +0 -0
  28. /package/meta/{cssui.bundle.min.css → templates/character-sheet-template/cssui.bundle.min.css} +0 -0
  29. /package/meta/{character-sheet-template.html → templates/character-sheet-template/index.html} +0 -0
  30. /package/meta/{character-sheet/js → templates/character-sheet-template}/main.js +0 -0
  31. /package/meta/{character-sheet/js → templates/character-sheet-template}/model.js +0 -0
  32. /package/meta/{search.js → templates/default-template/search.js} +0 -0
  33. /package/meta/{sticky.js → templates/default-template/sticky.js} +0 -0
  34. /package/meta/{toc-generator.js → templates/default-template/toc-generator.js} +0 -0
  35. /package/meta/{toc.js → templates/default-template/toc.js} +0 -0
  36. /package/meta/{template2.html → templates/template2/index.html} +0 -0
@@ -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
- });