@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/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,88 @@
|
|
|
1
|
+
# 0.77.0
|
|
2
|
+
2026-02-13
|
|
3
|
+
|
|
4
|
+
QOL:
|
|
5
|
+
- When 'serve' encounters an occupied port 8080, prompt the user to find an available port instead of just exiting with an error. Will check open ports and find the closest port to 8080, then ask the user if they want to use it.
|
|
6
|
+
|
|
7
|
+
Meta cleanup:
|
|
8
|
+
- Templates should have their own folder, including default
|
|
9
|
+
- All static files for a template should be in the template's folder, and probably in the right subfolder (e.g. public/default.css should be in templates/default/public/default.css or something like that).
|
|
10
|
+
- Template filenames should be templates/{templateName}/index.html
|
|
11
|
+
- Ursa should throw a warning if it finds orphaned static files in meta that aren't referenced by a template
|
|
12
|
+
|
|
13
|
+
Static assets revamp:
|
|
14
|
+
- Revamped the building of static assets (stylesheets and scripts). The new logic is:
|
|
15
|
+
- All meta scripts and stylesheets should be bundled together into a single CSS file and a single JS file for the entire site. This applies to build mode; in dev mode, they are served individually for easier debugging and regeneration.
|
|
16
|
+
- Bundle-able document files (style.css, script.js, menu.md) will be bundled together on a per-folder basis. Each document will include the bundles from its own folder and all parent folders (where they exist).
|
|
17
|
+
|
|
18
|
+
Regeneration revamp:
|
|
19
|
+
- Existing logic:
|
|
20
|
+
- On first generation, save a cache of document output given some sort of hash of the source file and metadata (e.g. mtime, size, etc.)
|
|
21
|
+
- All static files (meta and document) should include a datetime or hash-based cache-buster in their query strings / filenames, so they can be invalidated as needed
|
|
22
|
+
- On subsequent generations, if the source file's hash is unchanged, skip regeneration and reuse the existing output file. (Note: this doesn't handle cases where the statis files changed and the document didn't; see below)
|
|
23
|
+
- Push a notification to the client when a file is regenerated, so the client can update the page if it's currently being viewed
|
|
24
|
+
- New logic is as above, plus: (some of this is partially complete, but these are the complete requirements)
|
|
25
|
+
- When any file being watched is changed, determine the list of affected files. For instance:
|
|
26
|
+
- A normal document will obviously invalidate that exact document.
|
|
27
|
+
- Special Ursa static files (menu.md, style.css, and script.js) are inherited by all documents in the current folder and all subfolders, so they will invalidate all documents in the current folder and all subfolders.
|
|
28
|
+
- Meta static files:
|
|
29
|
+
- A template file in meta will invalidate all documents that use that template.
|
|
30
|
+
- A stylesheet or script file in meta will invalidate all documents that inherit from that meta (which is probably everything).
|
|
31
|
+
- All other static files in the docroot (assuming they're linked at all by any document) should be invalidated thus:
|
|
32
|
+
- Calculate a new hash for the static file
|
|
33
|
+
- Find all documents that reference that static file
|
|
34
|
+
- Regenerate the html (even if the source md/mdx/txt file is unchanged) for those documents to update the cache-busting query string for the static file reference
|
|
35
|
+
- This should catch all the various edge cases that previously required restarting the server or doing a full regeneration.
|
|
36
|
+
- Regeneration priority order:
|
|
37
|
+
- When regeneration is triggered, check for connected WebSocket clients and get their current URL.
|
|
38
|
+
- If their current URL is affected by the change (document, static asset, template, anything), prioritize the necessary documents and assets to serve that URL before all others, and when they are regenerated, send the push notification to reload.
|
|
39
|
+
- After that, regenerate the rest of the affected documents in the background.
|
|
40
|
+
- In cases of rapid changing of files, do the following:
|
|
41
|
+
- Debounce all file system change events within a short time window (e.g. 500ms); wait for at least 500ms of no changes before starting regeneration. This helps in cases where a script or bot is making many changes to many files.
|
|
42
|
+
- When a change is detected, before any complex processing, send a ping to WebSocket-connected clients that an update is in progress, but it isn't known yet if it will affect their page.
|
|
43
|
+
- When it is determined that a change will affect the current page of a connected client, send another push to let the client know.
|
|
44
|
+
- Handle the above two notifications in the UI thus:
|
|
45
|
+
- When updates start, add a subtle loading indicator to the right of all left widgets, such as <Loader color="gray" />
|
|
46
|
+
- If it turns out the update doesn't need a refresh, remove the indicator.
|
|
47
|
+
- If it does need a refresh, change the indicator to a <Loader color="green" />.
|
|
48
|
+
- When the hot refresh actually happens, the indicator shouldn't be there anymore.
|
|
49
|
+
- Cache changes (under consideration):
|
|
50
|
+
- Before the cache was implemented, every file change triggered a complete regeneration of the entire site. This was slow, obviously. First, an in-memory cache was added to speed up the regeneration of unchanged files. But since the watcher back then missed a lot of regeneration cases (such as all meta changes), killing 'serve' and restarting it was quite common. Thus, the cache was persisted to disk, so that even in the case of a full regeneration (such as after a restart), unchanged files would still be skipped. This has worked fairly well, but for the invalidation edge cases described above (already fixed).
|
|
51
|
+
- However, considering that we now have a robust regeneration system that can handle all edge cases and push updates to the client, we may want to consider removing the cache entirely. The cache adds complexity and can sometimes get into a bad state, requiring manual deletion. With the new regeneration system, we could keep the cache in-memory, and rarely will the user need to kill 'serve' and restart just to get an update (ideally, never). In-memory cache is even faster than disk, so this might be a better experience overall.
|
|
52
|
+
- Regeneration cases still unhandled:
|
|
53
|
+
- User updates Ursa itself (e.g. npm update) while serve is active. I mean, this shouldn't be very common outside of Ursa devs, but's determine:
|
|
54
|
+
- Does the current system actually catch the meta changes?
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
Top Menu improvements:
|
|
59
|
+
- When a submenu overflows the available viewport height, it should become scrollable instead of overflowing off the screen. This can be achieved with CSS by setting a max-height and overflow-y: auto on the submenu container.
|
|
60
|
+
|
|
61
|
+
New Widgets:
|
|
62
|
+
- Suggested Content
|
|
63
|
+
- A new left-side widget that shows a list of suggested content based on the current page. Categories of suggested content:
|
|
64
|
+
- Content you frequently view (uses localStorage to track page views and show most viewed content)
|
|
65
|
+
- Future ideas:
|
|
66
|
+
- LLM-guided suggestions based on frequently viewed content, suggested related documents you haven't viewed yet, etc.
|
|
67
|
+
|
|
68
|
+
Bugs:
|
|
69
|
+
- [x] When using menu.md with auto-generation, the top menu's Home href is "//index.html" instead of "/index.html". On localhost, this ends up working fine, but on https://realdomain.com, this loads https://index.html which obviously doesn't work. The current logic seems to prefer absolute URLs, so in this case, the url for home should be "/index.html" (not double slash).
|
|
70
|
+
- [x] Site style.css is not present on auto-generated index pages
|
|
71
|
+
- Regeneration issues:
|
|
72
|
+
- Create a power, that power page now exists. But powers.json doesn't have it.
|
|
73
|
+
|
|
74
|
+
# 0.76.0
|
|
75
|
+
2026-02-11
|
|
76
|
+
|
|
77
|
+
- **New Feature: Recent Activity widget.** A new topbar widget shows the 10 most recently modified documents in the docroot, sorted by modification date (most recent first). The widget appears on the left side of the top nav (to the right of the home icon) and is open by default.
|
|
78
|
+
- Recent activity data is collected during the generate phase by stat-ing each article file, then written to `public/recent-activity.json`.
|
|
79
|
+
- In serve/dev mode, the recent activity list is built during background cache initialization and updated live when article files are changed.
|
|
80
|
+
- The single-file regeneration path (`regenerateSingleFile`) also updates the recent activity JSON incrementally.
|
|
81
|
+
- **Widget system improvements:**
|
|
82
|
+
- All widgets now have a close (✕) button in the upper-right corner of their panel header. Clicking it closes the widget and deselects the corresponding icon in the top bar.
|
|
83
|
+
- Widget open/closed state is now persisted in localStorage. Widgets that were open will remain open after a page reload, and widgets that were closed will remain closed. Widgets with no saved state fall back to their default (Recent Activity defaults to open; others default to closed).
|
|
84
|
+
- The widget system now supports both left-side and right-side widget panels, which operate independently (one widget per side can be open at a time).
|
|
85
|
+
|
|
1
86
|
# 0.75.0
|
|
2
87
|
2026-02-10
|
|
3
88
|
|
package/meta/default.css
CHANGED
|
@@ -72,12 +72,19 @@ nav#nav-global {
|
|
|
72
72
|
background: none;
|
|
73
73
|
border: none;
|
|
74
74
|
cursor: pointer;
|
|
75
|
-
justify-self: start;
|
|
76
75
|
}
|
|
77
76
|
button.menu-button:hover {
|
|
78
77
|
opacity: 0.7;
|
|
79
78
|
}
|
|
80
79
|
|
|
80
|
+
/* Left controls: menu button + left-side widgets */
|
|
81
|
+
.nav-left-controls {
|
|
82
|
+
display: flex;
|
|
83
|
+
align-items: center;
|
|
84
|
+
justify-self: start;
|
|
85
|
+
gap: 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
81
88
|
/* Center container for search and top menu */
|
|
82
89
|
.nav-center {
|
|
83
90
|
width: calc(var(--article-width) + 38px);
|
|
@@ -128,7 +135,7 @@ nav#nav-global {
|
|
|
128
135
|
|
|
129
136
|
/* ==========================================
|
|
130
137
|
WIDGET SYSTEM STYLES
|
|
131
|
-
|
|
138
|
+
Nav widgets (TOC, Search, Profile, Recent Activity)
|
|
132
139
|
========================================== */
|
|
133
140
|
|
|
134
141
|
/* Widget bar in the nav right column */
|
|
@@ -145,6 +152,35 @@ nav#nav-global .nav-right-controls {
|
|
|
145
152
|
gap: 0;
|
|
146
153
|
}
|
|
147
154
|
|
|
155
|
+
/* Ursa update indicator (loading spinner in nav bar) */
|
|
156
|
+
.ursa-update-indicator {
|
|
157
|
+
display: none;
|
|
158
|
+
align-items: center;
|
|
159
|
+
justify-content: center;
|
|
160
|
+
width: var(--global-nav-height);
|
|
161
|
+
height: var(--global-nav-height);
|
|
162
|
+
opacity: 0.7;
|
|
163
|
+
}
|
|
164
|
+
.ursa-spinner {
|
|
165
|
+
width: 14px;
|
|
166
|
+
height: 14px;
|
|
167
|
+
border: 2px solid rgba(128, 128, 128, 0.3);
|
|
168
|
+
border-top-color: rgba(128, 128, 128, 0.8);
|
|
169
|
+
border-radius: 50%;
|
|
170
|
+
animation: ursa-spin 0.8s linear infinite;
|
|
171
|
+
}
|
|
172
|
+
.ursa-update-gray .ursa-spinner {
|
|
173
|
+
border-color: rgba(128, 128, 128, 0.3);
|
|
174
|
+
border-top-color: rgba(128, 128, 128, 0.8);
|
|
175
|
+
}
|
|
176
|
+
.ursa-update-green .ursa-spinner {
|
|
177
|
+
border-color: rgba(76, 175, 80, 0.3);
|
|
178
|
+
border-top-color: rgba(76, 175, 80, 0.9);
|
|
179
|
+
}
|
|
180
|
+
@keyframes ursa-spin {
|
|
181
|
+
to { transform: rotate(360deg); }
|
|
182
|
+
}
|
|
183
|
+
|
|
148
184
|
.widget-button {
|
|
149
185
|
display: flex;
|
|
150
186
|
align-items: center;
|
|
@@ -174,7 +210,7 @@ nav#nav-global .nav-right-controls {
|
|
|
174
210
|
pointer-events: none;
|
|
175
211
|
}
|
|
176
212
|
|
|
177
|
-
/* Widget dropdown panel */
|
|
213
|
+
/* Widget dropdown panel (right-side, default) */
|
|
178
214
|
.widget-dropdown {
|
|
179
215
|
position: fixed;
|
|
180
216
|
top: var(--global-nav-height);
|
|
@@ -190,10 +226,54 @@ nav#nav-global .nav-right-controls {
|
|
|
190
226
|
transition: opacity 0.15s ease;
|
|
191
227
|
}
|
|
192
228
|
|
|
229
|
+
/* Widget dropdown panel (left-side) */
|
|
230
|
+
.widget-dropdown.widget-dropdown-left {
|
|
231
|
+
right: auto;
|
|
232
|
+
left: 0;
|
|
233
|
+
border-left: none;
|
|
234
|
+
border-right: 1px solid var(--widget-border);
|
|
235
|
+
box-shadow: 2px 4px 12px rgba(0, 0, 0, 0.2);
|
|
236
|
+
}
|
|
237
|
+
|
|
193
238
|
.widget-dropdown.hidden {
|
|
194
239
|
display: none;
|
|
195
240
|
}
|
|
196
241
|
|
|
242
|
+
/* Widget header with title and close button */
|
|
243
|
+
.widget-header {
|
|
244
|
+
display: flex;
|
|
245
|
+
align-items: center;
|
|
246
|
+
justify-content: space-between;
|
|
247
|
+
padding: 0.5rem 0.75rem;
|
|
248
|
+
border-bottom: 1px solid var(--widget-border);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.widget-header-title {
|
|
252
|
+
font-weight: 600;
|
|
253
|
+
font-size: 0.85rem;
|
|
254
|
+
text-transform: uppercase;
|
|
255
|
+
letter-spacing: 0.03em;
|
|
256
|
+
opacity: 0.7;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.widget-close-btn {
|
|
260
|
+
background: none;
|
|
261
|
+
border: none;
|
|
262
|
+
color: var(--text-color);
|
|
263
|
+
font-size: 1rem;
|
|
264
|
+
cursor: pointer;
|
|
265
|
+
opacity: 0.5;
|
|
266
|
+
padding: 4px 8px;
|
|
267
|
+
line-height: 1;
|
|
268
|
+
border-radius: 3px;
|
|
269
|
+
transition: opacity 0.15s ease, background-color 0.15s ease;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.widget-close-btn:hover {
|
|
273
|
+
opacity: 1;
|
|
274
|
+
background-color: rgba(128, 128, 128, 0.2);
|
|
275
|
+
}
|
|
276
|
+
|
|
197
277
|
/* Widget content panels — only the active one is visible */
|
|
198
278
|
.widget-content {
|
|
199
279
|
display: none;
|
|
@@ -374,6 +454,68 @@ nav#nav-global .nav-right-controls {
|
|
|
374
454
|
font-style: italic;
|
|
375
455
|
}
|
|
376
456
|
|
|
457
|
+
/* --- Recent Activity Widget --- */
|
|
458
|
+
.recent-activity-list {
|
|
459
|
+
padding: 0;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
.recent-activity-loading,
|
|
463
|
+
.recent-activity-empty {
|
|
464
|
+
padding: 1.5rem 1rem;
|
|
465
|
+
text-align: center;
|
|
466
|
+
color: var(--text-color);
|
|
467
|
+
opacity: 0.6;
|
|
468
|
+
font-style: italic;
|
|
469
|
+
font-size: 0.85rem;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
.recent-activity-items {
|
|
473
|
+
list-style: none;
|
|
474
|
+
margin: 0;
|
|
475
|
+
padding: 0;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
.recent-activity-item {
|
|
479
|
+
display: flex;
|
|
480
|
+
align-items: baseline;
|
|
481
|
+
justify-content: space-between;
|
|
482
|
+
gap: 0.75rem;
|
|
483
|
+
padding: 0.5rem 0.75rem;
|
|
484
|
+
border-bottom: 1px solid var(--widget-border);
|
|
485
|
+
transition: background-color 0.15s ease;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
.recent-activity-item:last-child {
|
|
489
|
+
border-bottom: none;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
.recent-activity-item:hover {
|
|
493
|
+
background-color: rgba(128, 128, 128, 0.1);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
.recent-activity-link {
|
|
497
|
+
color: var(--text-color);
|
|
498
|
+
text-decoration: none;
|
|
499
|
+
font-size: 0.9rem;
|
|
500
|
+
flex: 1;
|
|
501
|
+
min-width: 0;
|
|
502
|
+
overflow: hidden;
|
|
503
|
+
text-overflow: ellipsis;
|
|
504
|
+
white-space: nowrap;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
.recent-activity-link:hover {
|
|
508
|
+
text-decoration: underline;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.recent-activity-time {
|
|
512
|
+
font-size: 0.75rem;
|
|
513
|
+
color: var(--text-color);
|
|
514
|
+
opacity: 0.5;
|
|
515
|
+
white-space: nowrap;
|
|
516
|
+
flex-shrink: 0;
|
|
517
|
+
}
|
|
518
|
+
|
|
377
519
|
/* Search functionality styles */
|
|
378
520
|
.search-results {
|
|
379
521
|
position: fixed;
|
|
@@ -558,6 +700,8 @@ nav#nav-main-top .top-menu-dropdown {
|
|
|
558
700
|
top: 100%;
|
|
559
701
|
left: 0;
|
|
560
702
|
min-width: 200px;
|
|
703
|
+
max-height: calc(100vh - var(--global-nav-height) - 20px);
|
|
704
|
+
overflow-y: auto;
|
|
561
705
|
background-color: var(--widget-bg);
|
|
562
706
|
border: 1px solid var(--widget-border);
|
|
563
707
|
border-radius: 4px;
|
|
@@ -612,6 +756,8 @@ nav#nav-main-top .top-menu-flyout {
|
|
|
612
756
|
left: 100%;
|
|
613
757
|
top: -1px;
|
|
614
758
|
min-width: 200px;
|
|
759
|
+
max-height: calc(100vh - var(--global-nav-height) - 20px);
|
|
760
|
+
overflow-y: auto;
|
|
615
761
|
background-color: var(--widget-bg);
|
|
616
762
|
border: 1px solid var(--widget-border);
|
|
617
763
|
border-radius: 4px;
|