mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-13 00:47:50 +00:00
Merge branch 'refs/heads/dev' into syntax-highlighter-unified
This commit is contained in:
commit
88d34a970c
42 files changed
+4602
-84
No files matched your search
@@ -0,0 +1,44 @@
|
||||
<script>
|
||||
(function() {
|
||||
// Fix uppercase text in table cells
|
||||
// Javadoc 11: method names are in th[scope="row"] a, not td a
|
||||
function fixCase() {
|
||||
document.querySelectorAll("th[scope='row'] a, td a, .memberNameLink a").forEach(function(el) {
|
||||
var t = el.textContent.trim();
|
||||
if (t.length > 2 && t === t.toUpperCase() && t.indexOf(" ") === -1) {
|
||||
el.textContent = t.toLowerCase();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Correct navPadding = topNav height + subNav height
|
||||
// Uses !important to override jQuery's inline style
|
||||
function fixPadding() {
|
||||
var topNav = document.querySelector(".topNav");
|
||||
var subNav = document.querySelector(".subNav");
|
||||
var pad = document.querySelector(".navPadding");
|
||||
if (!pad) return;
|
||||
var h = (topNav ? topNav.offsetHeight : 0) + (subNav ? subNav.offsetHeight : 0);
|
||||
if (h > 0) {
|
||||
pad.style.setProperty("padding-top", h + "px", "important");
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
fixCase();
|
||||
// Run fixPadding after jQuery has had its chance
|
||||
setTimeout(fixPadding, 0);
|
||||
setTimeout(fixPadding, 50);
|
||||
setTimeout(fixPadding, 200);
|
||||
|
||||
// Use MutationObserver to permanently override any further jQuery writes
|
||||
var pad = document.querySelector(".navPadding");
|
||||
if (pad) {
|
||||
var observer = new MutationObserver(function() {
|
||||
fixPadding();
|
||||
});
|
||||
observer.observe(pad, { attributes: true, attributeFilter: ["style"] });
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@@ -0,0 +1,11 @@
|
||||
<div id="javadoc-header">
|
||||
<a href="/CustomNPC-Plus/" class="header-brand">
|
||||
<div class="header-title">
|
||||
CustomNPC+
|
||||
<span>Javadoc Reference</span>
|
||||
</div>
|
||||
</a>
|
||||
<a href="/CustomNPC-Plus/" class="header-back">
|
||||
Back to Index
|
||||
</a>
|
||||
</div>
|
||||
@@ -0,0 +1,954 @@
|
||||
/* ============================================================
|
||||
CustomNPC+ — Javadoc Theme (Javadoc 11)
|
||||
Tokyo Night — Styled Edition
|
||||
============================================================ */
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap');
|
||||
|
||||
:root {
|
||||
/* ── Tokyo Night palette ── */
|
||||
--bg: #1a1b26;
|
||||
--bg2: #1f2335;
|
||||
--bg3: #24283b;
|
||||
--bg-elevated: #292e42;
|
||||
--border: #3b4261;
|
||||
--border2: #414868;
|
||||
--text: #c0caf5;
|
||||
--text-mid: #a9b1d6;
|
||||
--text-dim: #565f89;
|
||||
--green: #9ece6a;
|
||||
--green-dim: rgba(158, 206, 106, 0.12);
|
||||
--blue: #7aa2f7;
|
||||
--blue-dim: rgba(122, 162, 247, 0.12);
|
||||
--amber: #e0af68;
|
||||
--amber-dim: rgba(224, 175, 104, 0.10);
|
||||
--purple: #bb9af7;
|
||||
--purple-dim: rgba(187, 154, 247, 0.10);
|
||||
--cyan: #7dcfff;
|
||||
--cyan-dim: rgba(125, 207, 255, 0.10);
|
||||
--red: #f7768e;
|
||||
--highlight-bg: rgba(122, 162, 247, 0.08);
|
||||
--hover-bg: rgba(187, 154, 247, 0.08);
|
||||
--nav-h: 44px;
|
||||
--subnav-h: 36px;
|
||||
|
||||
/* ── Font stacks ── */
|
||||
--font-ui: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
--font-mono: 'JetBrains Mono', 'Cascadia Code', 'Fira Code', monospace;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box !important; }
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--bg) !important;
|
||||
color: var(--text) !important;
|
||||
font-family: var(--font-mono) !important;
|
||||
font-size: 13px !important;
|
||||
line-height: 1.6 !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
min-height: 100vh !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
}
|
||||
|
||||
.skipNav { display: none !important; }
|
||||
|
||||
/* ══════════════════════════════════════════════
|
||||
NAVIGATION
|
||||
══════════════════════════════════════════════ */
|
||||
|
||||
.fixedNav {
|
||||
position: fixed !important;
|
||||
top: 0 !important; left: 0 !important; right: 0 !important;
|
||||
z-index: 100 !important;
|
||||
}
|
||||
|
||||
/* Top bar — gradient accent stripe on top */
|
||||
.topNav {
|
||||
position: relative !important;
|
||||
background: var(--bg2) !important;
|
||||
border-bottom: 1px solid var(--border) !important;
|
||||
height: var(--nav-h) !important;
|
||||
margin: 0 !important; padding: 0 !important;
|
||||
display: flex !important;
|
||||
align-items: stretch !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
/* Gradient accent line across the very top */
|
||||
.topNav::before {
|
||||
content: '' !important;
|
||||
position: absolute !important;
|
||||
top: 0 !important; left: 0 !important; right: 0 !important;
|
||||
height: 2px !important;
|
||||
background: linear-gradient(90deg, var(--blue), var(--purple), var(--cyan)) !important;
|
||||
z-index: 10 !important;
|
||||
}
|
||||
|
||||
/* Nav list — leave room on right for brand */
|
||||
.topNav > ul.navList {
|
||||
list-style: none !important;
|
||||
margin: 0 !important;
|
||||
padding: 2px 0 0 8px !important;
|
||||
padding-right: 320px !important;
|
||||
display: flex !important;
|
||||
align-items: stretch !important;
|
||||
flex-shrink: 0 !important;
|
||||
}
|
||||
|
||||
.topNav > ul.navList > li {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 16px !important;
|
||||
height: var(--nav-h) !important;
|
||||
font-family: var(--font-ui) !important;
|
||||
font-size: 12px !important;
|
||||
font-weight: 500 !important;
|
||||
white-space: nowrap !important;
|
||||
text-transform: none !important;
|
||||
letter-spacing: 0.01em !important;
|
||||
color: var(--text-dim) !important;
|
||||
border-bottom: 2px solid transparent !important;
|
||||
transition: color 0.2s !important;
|
||||
}
|
||||
|
||||
.topNav > ul.navList > li > a {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
height: var(--nav-h) !important;
|
||||
margin: 0 -16px !important;
|
||||
padding: 0 16px !important;
|
||||
color: var(--text-dim) !important;
|
||||
font-family: var(--font-ui) !important;
|
||||
font-size: 12px !important;
|
||||
font-weight: 500 !important;
|
||||
text-decoration: none !important;
|
||||
text-transform: none !important;
|
||||
letter-spacing: 0.01em !important;
|
||||
white-space: nowrap !important;
|
||||
border-bottom: 2px solid transparent !important;
|
||||
transition: color 0.2s, border-color 0.2s !important;
|
||||
}
|
||||
|
||||
.topNav > ul.navList > li > a:hover {
|
||||
color: var(--text) !important;
|
||||
border-bottom-color: var(--purple) !important;
|
||||
background: transparent !important;
|
||||
text-shadow: none !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
/* Active item — gradient underline */
|
||||
.topNav > ul.navList > li.navBarCell1Rev {
|
||||
color: var(--text) !important;
|
||||
border-image: linear-gradient(90deg, var(--blue), var(--purple)) 1 !important;
|
||||
border-bottom-width: 2px !important;
|
||||
border-bottom-style: solid !important;
|
||||
}
|
||||
|
||||
/* ── Brand — pinned to far right of topNav ── */
|
||||
.topNav > .aboutLanguage {
|
||||
position: absolute !important;
|
||||
top: 0 !important; right: 0 !important;
|
||||
height: var(--nav-h) !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
gap: 12px !important;
|
||||
padding: 0 16px !important;
|
||||
background: var(--bg2) !important;
|
||||
border-left: 1px solid var(--border) !important;
|
||||
z-index: 5 !important;
|
||||
}
|
||||
|
||||
/* Hide the duplicate in bottom nav */
|
||||
.bottomNav > .aboutLanguage,
|
||||
.bottomNav .aboutLanguage {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#javadoc-header {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
gap: 12px !important;
|
||||
}
|
||||
|
||||
.header-brand { text-decoration: none !important; }
|
||||
|
||||
/* Brand title — gradient text */
|
||||
.header-title {
|
||||
font-family: var(--font-ui) !important;
|
||||
font-size: 14px !important;
|
||||
font-weight: 700 !important;
|
||||
background: linear-gradient(135deg, var(--blue), var(--purple)) !important;
|
||||
-webkit-background-clip: text !important;
|
||||
-webkit-text-fill-color: transparent !important;
|
||||
background-clip: text !important;
|
||||
line-height: 1.4 !important;
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
|
||||
.header-title span {
|
||||
display: block !important;
|
||||
font-family: var(--font-mono) !important;
|
||||
font-size: 9px !important;
|
||||
font-weight: 400 !important;
|
||||
background: none !important;
|
||||
-webkit-text-fill-color: var(--text-dim) !important;
|
||||
color: var(--text-dim) !important;
|
||||
margin-top: 1px !important;
|
||||
letter-spacing: 0.1em !important;
|
||||
text-transform: uppercase !important;
|
||||
}
|
||||
|
||||
.header-back {
|
||||
display: inline-flex !important;
|
||||
align-items: center !important;
|
||||
text-decoration: none !important;
|
||||
color: var(--text-dim) !important;
|
||||
font-family: var(--font-ui) !important;
|
||||
font-size: 11px !important;
|
||||
font-weight: 500 !important;
|
||||
padding: 5px 12px !important;
|
||||
border: 1px solid var(--border2) !important;
|
||||
border-radius: 6px !important;
|
||||
white-space: nowrap !important;
|
||||
transition: all 0.2s !important;
|
||||
}
|
||||
|
||||
.header-back:hover {
|
||||
color: var(--text) !important;
|
||||
border-color: var(--purple) !important;
|
||||
background: var(--purple-dim) !important;
|
||||
text-decoration: none !important;
|
||||
text-shadow: none !important;
|
||||
}
|
||||
|
||||
/* ── Sub nav ── */
|
||||
.subNav {
|
||||
background: var(--bg3) !important;
|
||||
border-bottom: 1px solid var(--border) !important;
|
||||
height: var(--subnav-h) !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 14px !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
gap: 12px !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
/* Search first */
|
||||
.subNav > ul.navListSearch {
|
||||
order: -1 !important;
|
||||
}
|
||||
|
||||
.subNav ul,
|
||||
.subNav ul.navList,
|
||||
.subNav ul.subNavList,
|
||||
.subNav ul.navListSearch {
|
||||
list-style: none !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
gap: 0 !important;
|
||||
}
|
||||
|
||||
.subNav li {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
font-family: var(--font-ui) !important;
|
||||
font-size: 11px !important;
|
||||
color: var(--text-dim) !important;
|
||||
padding: 0 6px !important;
|
||||
white-space: nowrap !important;
|
||||
border: none !important;
|
||||
height: auto !important;
|
||||
text-transform: none !important;
|
||||
}
|
||||
|
||||
.subNav a {
|
||||
color: var(--blue) !important;
|
||||
text-decoration: none !important;
|
||||
font-size: 11px !important;
|
||||
font-family: var(--font-ui) !important;
|
||||
transition: color 0.15s !important;
|
||||
}
|
||||
|
||||
.subNav a:hover { color: var(--purple) !important; text-shadow: none !important; }
|
||||
|
||||
.subNav label {
|
||||
font-family: var(--font-ui) !important;
|
||||
font-size: 11px !important;
|
||||
color: var(--text-dim) !important;
|
||||
text-transform: none !important;
|
||||
margin-right: 4px !important;
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════
|
||||
CONTENT
|
||||
══════════════════════════════════════════════ */
|
||||
|
||||
main {
|
||||
background: var(--bg) !important;
|
||||
padding: 28px !important;
|
||||
flex: 1 0 auto !important;
|
||||
}
|
||||
|
||||
.contentContainer, div.contentContainer {
|
||||
background: var(--bg) !important;
|
||||
padding: 0 28px 0px !important;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 0 28px 0 !important;
|
||||
margin-bottom: 20px !important;
|
||||
}
|
||||
|
||||
/* Subtitle under h1 on help/overview pages */
|
||||
.header .subTitle,
|
||||
div.subTitle {
|
||||
color: var(--text-mid) !important;
|
||||
font-family: var(--font-ui) !important;
|
||||
font-size: 13px !important;
|
||||
line-height: 1.7 !important;
|
||||
margin-top: 8px !important;
|
||||
}
|
||||
|
||||
.packageLabelInType {
|
||||
font-family: var(--font-mono) !important;
|
||||
font-size: 11px !important;
|
||||
color: var(--text-dim) !important;
|
||||
}
|
||||
|
||||
/* ── Headings ── */
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: var(--font-ui) !important;
|
||||
font-weight: 600 !important;
|
||||
line-height: 1.5 !important;
|
||||
text-transform: none !important;
|
||||
letter-spacing: -0.01em !important;
|
||||
}
|
||||
|
||||
/* Page title — gradient accent bar */
|
||||
h1.title, h2.title {
|
||||
font-size: 22px !important;
|
||||
font-weight: 700 !important;
|
||||
color: var(--text) !important;
|
||||
border-bottom: none !important;
|
||||
padding-bottom: 16px !important;
|
||||
padding-left: 16px !important;
|
||||
margin-bottom: 24px !important;
|
||||
margin-top: 0 !important;
|
||||
border-left: 4px solid transparent !important;
|
||||
border-image: linear-gradient(180deg, var(--blue), var(--purple)) 1 !important;
|
||||
}
|
||||
|
||||
/* Section h2 — colored panel */
|
||||
section > h2,
|
||||
li.blockList > section > h2 {
|
||||
font-size: 14px !important;
|
||||
color: var(--green) !important;
|
||||
border-left: 3px solid var(--green) !important;
|
||||
border-bottom: none !important;
|
||||
padding: 10px 14px !important;
|
||||
margin: 0 !important;
|
||||
background: var(--green-dim) !important;
|
||||
}
|
||||
|
||||
/* Generic h2 (table section headers like "Method Summary") */
|
||||
h2 {
|
||||
font-size: 14px !important;
|
||||
color: var(--amber) !important;
|
||||
border-bottom: none !important;
|
||||
border-left: 3px solid var(--amber) !important;
|
||||
padding: 8px 12px !important;
|
||||
margin-top: 28px !important;
|
||||
margin-bottom: 12px !important;
|
||||
background: var(--amber-dim) !important;
|
||||
}
|
||||
|
||||
/* h3 — table section headers inside blockList */
|
||||
h3 {
|
||||
font-size: 12px !important;
|
||||
color: var(--text-mid) !important;
|
||||
margin: 0 !important;
|
||||
padding: 10px 14px !important;
|
||||
background: var(--bg3) !important;
|
||||
border-bottom: 1px solid var(--border) !important;
|
||||
}
|
||||
|
||||
/* h4 — method detail names */
|
||||
h4 {
|
||||
font-size: 13px !important;
|
||||
font-weight: 600 !important;
|
||||
color: var(--cyan) !important;
|
||||
margin: 0 !important;
|
||||
padding: 10px 14px !important;
|
||||
background: var(--bg3) !important;
|
||||
border-bottom: 1px solid var(--border) !important;
|
||||
border-left: 3px solid var(--cyan) !important;
|
||||
}
|
||||
|
||||
/* ── blockList sections — card-like containers ── */
|
||||
ul.blockList > li.blockList,
|
||||
ul.blockListLast > li.blockList {
|
||||
border: 1px solid var(--border) !important;
|
||||
border-radius: 6px !important;
|
||||
margin-bottom: 14px !important;
|
||||
padding: 0 !important;
|
||||
background: var(--bg2) !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
/* Help page: section cards with padding around their content */
|
||||
ul.blockList > li.blockList > section {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
ul.blockList > li.blockList > section > p,
|
||||
ul.blockList > li.blockList > section > ul:not(.blockList):not(.blockListLast) {
|
||||
padding: 14px 18px !important;
|
||||
margin: 0 !important;
|
||||
color: var(--text-mid) !important;
|
||||
font-family: var(--font-ui) !important;
|
||||
font-size: 13px !important;
|
||||
line-height: 1.7 !important;
|
||||
}
|
||||
|
||||
/* Nested lists inside section content */
|
||||
ul.blockList > li.blockList > section > ul:not(.blockList):not(.blockListLast) > li {
|
||||
color: var(--text-mid) !important;
|
||||
font-family: var(--font-ui) !important;
|
||||
font-size: 13px !important;
|
||||
padding: 3px 0 !important;
|
||||
list-style: none !important;
|
||||
}
|
||||
|
||||
ul.blockList > li.blockList > section > ul:not(.blockList):not(.blockListLast) > li::before {
|
||||
content: '\203A' !important;
|
||||
color: var(--purple) !important;
|
||||
margin-right: 8px !important;
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
|
||||
/* ── Inherited methods block ── */
|
||||
ul.blockList > li.blockList > h3,
|
||||
ul.blockList > li.blockList > ul.blockList > li.blockList > h3 {
|
||||
font-size: 11px !important;
|
||||
color: var(--text-dim) !important;
|
||||
padding: 8px 14px !important;
|
||||
background: var(--bg3) !important;
|
||||
border-bottom: 1px solid var(--border) !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
/* The comma-separated code block of inherited method names */
|
||||
ul.blockList > li.blockList > code,
|
||||
li.blockList > code {
|
||||
display: block !important;
|
||||
padding: 10px 16px !important;
|
||||
color: var(--text-dim) !important;
|
||||
font-family: var(--font-mono) !important;
|
||||
font-size: 12px !important;
|
||||
background: var(--bg) !important;
|
||||
border: none !important;
|
||||
line-height: 1.8 !important;
|
||||
}
|
||||
|
||||
/* ── Inheritance chain ── */
|
||||
ul.inheritance {
|
||||
list-style: none !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 0 16px !important;
|
||||
font-size: 12px !important;
|
||||
color: var(--text-dim) !important;
|
||||
}
|
||||
ul.inheritance li { padding-left: 16px !important; }
|
||||
ul.inheritance li:first-child { padding-left: 0 !important; }
|
||||
|
||||
/* ── General content lists (help page) ── */
|
||||
main ul:not(.blockList):not(.blockListLast):not(.navList):not(.subNavList):not(.navListSearch):not(.inheritance) {
|
||||
padding-left: 0 !important;
|
||||
margin: 4px 0 4px 12px !important;
|
||||
list-style: none !important;
|
||||
}
|
||||
main ul:not(.blockList):not(.blockListLast):not(.navList):not(.subNavList):not(.navListSearch):not(.inheritance) > li {
|
||||
color: var(--text-mid) !important;
|
||||
font-family: var(--font-ui) !important;
|
||||
font-size: 13px !important;
|
||||
padding: 3px 0 !important;
|
||||
line-height: 1.7 !important;
|
||||
}
|
||||
main ul:not(.blockList):not(.blockListLast):not(.navList):not(.subNavList):not(.navListSearch):not(.inheritance) > li::before {
|
||||
content: '\203A' !important;
|
||||
color: var(--purple) !important;
|
||||
margin-right: 8px !important;
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
|
||||
/* General paragraphs in content */
|
||||
main > div p, .contentContainer p {
|
||||
color: var(--text-mid) !important;
|
||||
font-family: var(--font-ui) !important;
|
||||
font-size: 13px !important;
|
||||
line-height: 1.7 !important;
|
||||
margin: 8px 0 !important;
|
||||
}
|
||||
|
||||
/* Emphasized phrase */
|
||||
.emphasizedPhrase {
|
||||
display: block !important;
|
||||
color: var(--text-dim) !important;
|
||||
font-family: var(--font-mono) !important;
|
||||
font-size: 12px !important;
|
||||
font-style: italic !important;
|
||||
border-left: 2px solid var(--purple) !important;
|
||||
padding: 8px 14px !important;
|
||||
margin-top: 16px !important;
|
||||
background: var(--purple-dim) !important;
|
||||
border-radius: 0 4px 4px 0 !important;
|
||||
}
|
||||
|
||||
/* Bottom footer subNav */
|
||||
footer {
|
||||
/*margin-top: auto;*/
|
||||
}
|
||||
footer .subNav {
|
||||
background: var(--bg3) !important;
|
||||
border-top: 1px solid var(--border) !important;
|
||||
border-bottom: none !important;
|
||||
height: var(--subnav-h) !important;
|
||||
padding: 0 14px !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
gap: 12px !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════
|
||||
TABLES
|
||||
══════════════════════════════════════════════ */
|
||||
|
||||
table {
|
||||
width: 100% !important;
|
||||
border-collapse: collapse !important;
|
||||
border: 1px solid var(--border) !important;
|
||||
border-radius: 6px !important;
|
||||
margin: 16px 0 !important;
|
||||
font-size: 13px !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
caption {
|
||||
background: var(--bg3) !important;
|
||||
padding: 8px 14px !important;
|
||||
text-align: left !important;
|
||||
border-bottom: 1px solid var(--border2) !important;
|
||||
caption-side: top !important;
|
||||
}
|
||||
|
||||
/* Table tabs — pill style */
|
||||
.activeTableTab, .tableTab {
|
||||
font-family: var(--font-ui) !important;
|
||||
font-size: 11px !important;
|
||||
font-weight: 500 !important;
|
||||
text-transform: none !important;
|
||||
padding: 4px 12px !important;
|
||||
border: 1px solid var(--border2) !important;
|
||||
border-radius: 20px !important;
|
||||
display: inline-block !important;
|
||||
margin-right: 6px !important;
|
||||
cursor: pointer !important;
|
||||
transition: all 0.2s !important;
|
||||
}
|
||||
|
||||
.activeTableTab {
|
||||
color: var(--text) !important;
|
||||
border-color: var(--blue) !important;
|
||||
background: var(--blue-dim) !important;
|
||||
}
|
||||
|
||||
.tableTab { color: var(--text-dim) !important; background: var(--bg3) !important; }
|
||||
.tableTab a { color: var(--text-dim) !important; text-decoration: none !important; }
|
||||
.tableTab a:hover { color: var(--text) !important; text-shadow: none !important; }
|
||||
|
||||
/* Column headers */
|
||||
th[scope="col"] {
|
||||
background: var(--bg-elevated) !important;
|
||||
color: var(--text-dim) !important;
|
||||
font-family: var(--font-ui) !important;
|
||||
font-size: 10px !important;
|
||||
font-weight: 600 !important;
|
||||
letter-spacing: 0.06em !important;
|
||||
text-transform: uppercase !important;
|
||||
padding: 10px 14px !important;
|
||||
border-bottom: 2px solid var(--border2) !important;
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
/* Data cells */
|
||||
th[scope="row"], td {
|
||||
background: var(--bg2) !important;
|
||||
color: var(--text) !important;
|
||||
padding: 10px 14px !important;
|
||||
border-bottom: 1px solid var(--border) !important;
|
||||
vertical-align: top !important;
|
||||
text-align: left !important;
|
||||
font-family: var(--font-mono) !important;
|
||||
font-size: 12px !important;
|
||||
font-weight: 400 !important;
|
||||
text-transform: none !important;
|
||||
letter-spacing: 0 !important;
|
||||
transition: background 0.15s !important;
|
||||
}
|
||||
|
||||
/* Stronger alternating row contrast */
|
||||
tr.altColor > td, tr.altColor > th[scope="row"] { background: var(--bg) !important; }
|
||||
tr.rowColor > td, tr.rowColor > th[scope="row"] { background: var(--bg2) !important; }
|
||||
|
||||
tr:hover > td, tr:hover > th[scope="row"] {
|
||||
background: var(--purple-dim) !important;
|
||||
border-bottom-color: rgba(187, 154, 247, 0.25) !important;
|
||||
}
|
||||
tr:hover > td a, tr:hover > th[scope="row"] a {
|
||||
color: var(--purple) !important;
|
||||
}
|
||||
|
||||
/* ── Links ── */
|
||||
a, a:visited { color: var(--blue) !important; text-decoration: none !important; transition: color 0.15s !important; }
|
||||
a:hover { color: var(--purple) !important; text-decoration: none !important; }
|
||||
.memberNameLink a { color: var(--cyan) !important; font-weight: 500 !important; }
|
||||
.memberNameLink a:hover { color: var(--purple) !important; }
|
||||
.externalLink { color: var(--blue) !important; }
|
||||
|
||||
/* ── Description blocks ── */
|
||||
div.block {
|
||||
color: var(--text-mid) !important;
|
||||
font-family: var(--font-ui) !important;
|
||||
font-size: 13px !important;
|
||||
line-height: 1.7 !important;
|
||||
padding: 4px 0 !important;
|
||||
}
|
||||
ul.blockList > li.blockList > div.block{ padding: 12px 16px !important; margin: 0 !important; }
|
||||
|
||||
/* ── Method detail ── */
|
||||
ul.blockList, ul.blockListLast { list-style: none !important; padding: 0 !important; margin: 0 !important; }
|
||||
ul.blockList > li.blockList, ul.blockListLast > li.blockList {
|
||||
border: 1px solid var(--border) !important;
|
||||
border-radius: 6px !important;
|
||||
margin-bottom: 12px !important;
|
||||
padding: 0 !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
/* Method/type signatures — accent left bar */
|
||||
pre.methodSignature, pre.typeSignature {
|
||||
background: var(--bg2) !important;
|
||||
border: none !important;
|
||||
border-bottom: 1px solid var(--border) !important;
|
||||
border-left: 3px solid var(--cyan) !important;
|
||||
margin: 0 !important;
|
||||
padding: 14px 18px !important;
|
||||
font-family: var(--font-mono) !important;
|
||||
font-size: 12px !important;
|
||||
color: var(--text) !important;
|
||||
overflow-x: auto !important;
|
||||
white-space: pre-wrap !important;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: var(--bg2) !important;
|
||||
border: 1px solid var(--border) !important;
|
||||
border-left: 3px solid var(--blue) !important;
|
||||
border-radius: 6px !important;
|
||||
padding: 14px 18px !important;
|
||||
font-family: var(--font-mono) !important;
|
||||
font-size: 12px !important;
|
||||
color: var(--text) !important;
|
||||
overflow-x: auto !important;
|
||||
margin: 0 !important;
|
||||
white-space: pre-wrap !important;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: var(--font-mono) !important;
|
||||
font-size: 12px !important;
|
||||
color: var(--cyan) !important;
|
||||
background: var(--bg3) !important;
|
||||
padding: 2px 6px !important;
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
|
||||
dl { padding: 12px 16px !important; margin: 0 !important; }
|
||||
dt { margin-bottom: 4px !important; }
|
||||
dd { margin: 0 0 8px 16px !important; color: var(--text-mid) !important; font-size: 12px !important; }
|
||||
|
||||
.paramLabel, .returnLabel, .throwsLabel, .seeLabel, .simpleTagLabel {
|
||||
font-family: var(--font-ui) !important;
|
||||
font-size: 10px !important;
|
||||
font-weight: 600 !important;
|
||||
color: var(--purple) !important;
|
||||
text-transform: uppercase !important;
|
||||
letter-spacing: 0.06em !important;
|
||||
}
|
||||
|
||||
.typeNameLabel { color: var(--cyan) !important; }
|
||||
|
||||
.deprecationBlock {
|
||||
background: var(--amber-dim) !important;
|
||||
border-left: 3px solid var(--amber) !important;
|
||||
border-radius: 0 6px 6px 0 !important;
|
||||
padding: 10px 14px !important;
|
||||
color: var(--amber) !important;
|
||||
font-size: 12px !important;
|
||||
}
|
||||
|
||||
/* ── Inputs ── */
|
||||
input[type="text"] {
|
||||
background: var(--bg) !important;
|
||||
border: 1px solid var(--border2) !important;
|
||||
color: var(--text) !important;
|
||||
font-family: var(--font-mono) !important;
|
||||
font-size: 12px !important;
|
||||
padding: 5px 12px !important;
|
||||
outline: none !important;
|
||||
border-radius: 6px !important;
|
||||
transition: border-color 0.2s, box-shadow 0.2s !important;
|
||||
}
|
||||
input[type="text"]:focus {
|
||||
border-color: var(--blue) !important;
|
||||
box-shadow: 0 0 0 2px var(--blue-dim) !important;
|
||||
}
|
||||
input[type="reset"] {
|
||||
background: var(--bg3) !important;
|
||||
border: 1px solid var(--border2) !important;
|
||||
color: var(--text-dim) !important;
|
||||
font-family: var(--font-ui) !important;
|
||||
font-size: 11px !important;
|
||||
padding: 5px 12px !important;
|
||||
cursor: pointer !important;
|
||||
border-radius: 6px !important;
|
||||
transition: all 0.2s !important;
|
||||
}
|
||||
input[type="reset"]:hover {
|
||||
border-color: var(--text-dim) !important;
|
||||
color: var(--text-mid) !important;
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════
|
||||
BOTTOM NAV
|
||||
══════════════════════════════════════════════ */
|
||||
|
||||
.bottomNav {
|
||||
background: var(--bg2) !important;
|
||||
border-top: 1px solid var(--border) !important;
|
||||
min-height: var(--nav-h) !important;
|
||||
padding: 0 !important;
|
||||
margin-top: 32px !important;
|
||||
overflow: hidden !important;
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
/* Gradient accent line on bottom nav too */
|
||||
.bottomNav::after {
|
||||
content: '' !important;
|
||||
position: absolute !important;
|
||||
bottom: 0 !important; left: 0 !important; right: 0 !important;
|
||||
height: 2px !important;
|
||||
background: linear-gradient(90deg, var(--blue), var(--purple), var(--cyan)) !important;
|
||||
}
|
||||
|
||||
.bottomNav ul.navList[title="Navigation"] {
|
||||
list-style: none !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 0 0 8px !important;
|
||||
display: flex !important;
|
||||
align-items: stretch !important;
|
||||
height: var(--nav-h) !important;
|
||||
}
|
||||
|
||||
.bottomNav ul.navList[title="Navigation"] li {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 16px !important;
|
||||
height: var(--nav-h) !important;
|
||||
font-family: var(--font-ui) !important;
|
||||
font-size: 12px !important;
|
||||
font-weight: 500 !important;
|
||||
white-space: nowrap !important;
|
||||
text-transform: none !important;
|
||||
letter-spacing: 0.01em !important;
|
||||
color: var(--text-dim) !important;
|
||||
border-top: 2px solid transparent !important;
|
||||
transition: color 0.2s !important;
|
||||
}
|
||||
|
||||
.bottomNav ul.navList[title="Navigation"] li > a {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
height: var(--nav-h) !important;
|
||||
margin: 0 -16px !important;
|
||||
padding: 0 16px !important;
|
||||
color: var(--text-dim) !important;
|
||||
text-decoration: none !important;
|
||||
font-family: var(--font-ui) !important;
|
||||
font-size: 12px !important;
|
||||
font-weight: 500 !important;
|
||||
transition: color 0.2s !important;
|
||||
}
|
||||
|
||||
.bottomNav ul.navList[title="Navigation"] li > a:hover {
|
||||
color: var(--text) !important;
|
||||
text-shadow: none !important;
|
||||
}
|
||||
|
||||
.bottomNav ul.navList[title="Navigation"] li.navBarCell1Rev {
|
||||
color: var(--text) !important;
|
||||
border-image: linear-gradient(90deg, var(--blue), var(--purple)) 1 !important;
|
||||
border-top-width: 2px !important;
|
||||
border-top-style: solid !important;
|
||||
}
|
||||
|
||||
.bottomNav .aboutLanguage {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.legalCopy {
|
||||
background: var(--bg2) !important;
|
||||
color: var(--text-dim) !important;
|
||||
font-size: 11px !important;
|
||||
font-family: var(--font-ui) !important;
|
||||
padding: 14px 18px !important;
|
||||
text-align: center !important;
|
||||
border-top: 1px solid var(--border) !important;
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════
|
||||
SEARCH RESULTS DROPDOWN (jQuery UI Autocomplete)
|
||||
══════════════════════════════════════════════ */
|
||||
|
||||
.ui-autocomplete {
|
||||
background: var(--bg2) !important;
|
||||
border: 1px solid var(--border2) !important;
|
||||
border-radius: 8px !important;
|
||||
max-height: 400px !important;
|
||||
overflow-y: auto !important;
|
||||
overflow-x: hidden !important;
|
||||
z-index: 9999 !important;
|
||||
list-style: none !important;
|
||||
padding: 4px 0 !important;
|
||||
margin: 4px 0 0 !important;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(122, 162, 247, 0.05) !important;
|
||||
}
|
||||
|
||||
/* Category headers */
|
||||
.ui-autocomplete .ui-autocomplete-category {
|
||||
background: var(--bg-elevated) !important;
|
||||
color: var(--text-dim) !important;
|
||||
font-family: var(--font-ui) !important;
|
||||
font-size: 10px !important;
|
||||
font-weight: 600 !important;
|
||||
text-transform: uppercase !important;
|
||||
letter-spacing: 0.06em !important;
|
||||
padding: 6px 14px !important;
|
||||
border-bottom: 1px solid var(--border) !important;
|
||||
border-top: 1px solid var(--border) !important;
|
||||
cursor: default !important;
|
||||
}
|
||||
|
||||
/* Individual result items */
|
||||
.ui-autocomplete .ui-menu-item {
|
||||
background: var(--bg2) !important;
|
||||
margin: 0 !important;
|
||||
list-style: none !important;
|
||||
}
|
||||
|
||||
.ui-autocomplete .ui-menu-item > div,
|
||||
.ui-autocomplete .ui-menu-item-wrapper {
|
||||
background: var(--bg2) !important;
|
||||
color: var(--text) !important;
|
||||
font-family: var(--font-mono) !important;
|
||||
font-size: 12px !important;
|
||||
padding: 7px 14px !important;
|
||||
border: none !important;
|
||||
border-left: 2px solid transparent !important;
|
||||
cursor: pointer !important;
|
||||
line-height: 1.5 !important;
|
||||
transition: all 0.1s !important;
|
||||
}
|
||||
|
||||
/* Hover / focus state */
|
||||
.ui-autocomplete .ui-menu-item > div.ui-state-active,
|
||||
.ui-autocomplete .ui-menu-item > div.ui-state-focus,
|
||||
.ui-autocomplete .ui-menu-item-wrapper.ui-state-active,
|
||||
.ui-autocomplete .ui-menu-item-wrapper.ui-state-focus,
|
||||
.ui-autocomplete .ui-state-active,
|
||||
.ui-autocomplete .ui-state-focus {
|
||||
background: var(--purple-dim) !important;
|
||||
color: var(--text) !important;
|
||||
border: none !important;
|
||||
border-left: 2px solid var(--purple) !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
/* Highlighted matching text */
|
||||
.resultHighlight {
|
||||
color: var(--cyan) !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
/* Search tag result extras */
|
||||
.searchTagHolderResult {
|
||||
color: var(--text-dim) !important;
|
||||
font-size: 11px !important;
|
||||
}
|
||||
|
||||
.searchTagDescResult {
|
||||
color: var(--text-dim) !important;
|
||||
font-size: 11px !important;
|
||||
font-style: italic !important;
|
||||
}
|
||||
|
||||
/* "No results found" */
|
||||
.ui-autocomplete .ui-menu-item:only-child > div {
|
||||
color: var(--text-dim) !important;
|
||||
font-style: italic !important;
|
||||
}
|
||||
|
||||
/* Override jQuery UI defaults */
|
||||
.ui-widget {
|
||||
font-family: var(--font-mono) !important;
|
||||
}
|
||||
|
||||
.ui-widget-content {
|
||||
background: var(--bg2) !important;
|
||||
color: var(--text) !important;
|
||||
border-color: var(--border2) !important;
|
||||
}
|
||||
|
||||
.ui-menu .ui-menu-item {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
/* Watermark style for search input placeholder */
|
||||
.watermark {
|
||||
color: var(--text-dim) !important;
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════
|
||||
MISC
|
||||
══════════════════════════════════════════════ */
|
||||
|
||||
::-webkit-scrollbar { width: 8px; height: 8px; }
|
||||
::-webkit-scrollbar-track { background: var(--bg); }
|
||||
::-webkit-scrollbar-thumb { background: var(--border2); border-radius: 4px; }
|
||||
::-webkit-scrollbar-thumb:hover { background: var(--text-dim); }
|
||||
::selection { background: rgba(187, 154, 247, 0.25); color: var(--text); }
|
||||
hr { border: none !important; border-top: 1px solid var(--border) !important; margin: 24px 0 !important; }
|
||||
@@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Javadoc Index</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500;600&family=IBM+Plex+Sans:ital,wght@0,300;0,600;1,300&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
Generated
+1679
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "javadoc-index",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"vite": "^7.3.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import styles from './App.module.css'
|
||||
import { useDocs } from './useDocs'
|
||||
import TerminalHeader from './components/TerminalHeader'
|
||||
import Section from './components/Section'
|
||||
import { ReleaseCard, ExperimentalCard } from './components/Card'
|
||||
import { LoadingState, ErrorState, EmptyState } from './components/States'
|
||||
import Footer from './components/Footer'
|
||||
|
||||
export default function App() {
|
||||
const { data, loading, error } = useDocs()
|
||||
|
||||
const releases = data?.releases || []
|
||||
const experimental = data?.experimental || []
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<TerminalHeader />
|
||||
|
||||
{/* ── Releases ── */}
|
||||
<Section
|
||||
label="Release"
|
||||
variant="releases"
|
||||
count={loading ? undefined : releases.length}
|
||||
>
|
||||
{loading ? (
|
||||
<LoadingState />
|
||||
) : error ? (
|
||||
<ErrorState message={error} />
|
||||
) : releases.length === 0 ? (
|
||||
<EmptyState label="releases" />
|
||||
) : (
|
||||
<div className={styles.grid}>
|
||||
{releases.map((r, i) => (
|
||||
<ReleaseCard key={r.id} release={r} index={i} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
|
||||
{/* ── Experimental ── */}
|
||||
<Section
|
||||
label="Experimental"
|
||||
variant="experimental"
|
||||
count={loading ? undefined : experimental.length}
|
||||
>
|
||||
{loading ? (
|
||||
<LoadingState />
|
||||
) : error ? null : experimental.length === 0 ? (
|
||||
<EmptyState label="experimental branches" />
|
||||
) : (
|
||||
<div className={styles.grid}>
|
||||
{experimental.map((b, i) => (
|
||||
<ExperimentalCard key={b.id} branch={b} index={i} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
|
||||
{!loading && !error && (
|
||||
<Footer source={data?.source} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
.root {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
padding: 48px 32px 96px;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.root { padding: 24px 16px 64px; }
|
||||
.grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import styles from './Card.module.css'
|
||||
import { REPO_NAME } from '../config'
|
||||
|
||||
const BASE_PATH = `/${REPO_NAME}`
|
||||
|
||||
function formatDate(iso) {
|
||||
if (!iso) return null
|
||||
try {
|
||||
return new Intl.DateTimeFormat('en-GB', {
|
||||
day: '2-digit', month: 'short', year: 'numeric'
|
||||
}).format(new Date(iso))
|
||||
} catch (e) { return null }
|
||||
}
|
||||
|
||||
export function ReleaseCard({ release, index }) {
|
||||
const isLatest = release.isLatest
|
||||
const date = formatDate(release.date)
|
||||
const cardClass = isLatest
|
||||
? styles.card + ' ' + styles.latest
|
||||
: styles.card + ' ' + styles.release
|
||||
|
||||
return (
|
||||
<a
|
||||
href={BASE_PATH + '/' + release.path + '/'}
|
||||
className={cardClass}
|
||||
style={{ animationDelay: (index * 50) + 'ms' }}
|
||||
>
|
||||
<div className={styles.header}>
|
||||
<div className={styles.badges}>
|
||||
{isLatest && (
|
||||
<span className={styles.badge + ' ' + styles.badgeLatest}>● latest</span>
|
||||
)}
|
||||
<span className={styles.badge + ' ' + styles.badgeRelease}>release</span>
|
||||
</div>
|
||||
<span className={styles.arrow}>→</span>
|
||||
</div>
|
||||
|
||||
<div className={styles.version}>
|
||||
v{release.version}
|
||||
</div>
|
||||
<div className={styles.meta}>
|
||||
{date && (
|
||||
<div className={styles.metaRow}>
|
||||
<span className={styles.metaKey}>date</span>
|
||||
<span className={styles.metaVal}>{date}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.metaRow}>
|
||||
<span className={styles.metaKey}>path</span>
|
||||
<span className={styles.metaVal}>{release.path}/</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
export function ExperimentalCard({ branch, index }) {
|
||||
const date = formatDate(branch.date)
|
||||
|
||||
return (
|
||||
<a
|
||||
href={BASE_PATH + '/' + branch.path + '/'}
|
||||
className={styles.card + ' ' + styles.experimental}
|
||||
style={{ animationDelay: (index * 50) + 'ms' }}
|
||||
>
|
||||
<div className={styles.header}>
|
||||
<span className={styles.badge + ' ' + styles.badgeExperimental}>
|
||||
experimental
|
||||
</span>
|
||||
<span className={styles.arrow}>→</span>
|
||||
</div>
|
||||
|
||||
<div className={styles.version + ' ' + styles.versionBranch}>
|
||||
{branch.branch}
|
||||
</div>
|
||||
|
||||
<div className={styles.meta}>
|
||||
{date && (
|
||||
<div className={styles.metaRow}>
|
||||
<span className={styles.metaKey}>date</span>
|
||||
<span className={styles.metaVal}>{date}</span>
|
||||
</div>
|
||||
)}
|
||||
{branch.hash && (
|
||||
<div className={styles.metaRow}>
|
||||
<span className={styles.metaKey}>sha</span>
|
||||
<span className={styles.metaVal + ' ' + styles.hash}>{branch.hash}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.metaRow}>
|
||||
<span className={styles.metaKey}>path</span>
|
||||
<span className={styles.metaVal}>{branch.path}/</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
.card {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
background: var(--bg2);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
padding: 20px 22px;
|
||||
transition: border-color 0.15s, background 0.15s, transform 0.15s;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
animation: fadeUp 0.35s ease both;
|
||||
}
|
||||
|
||||
.card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0;
|
||||
height: 2px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
|
||||
.release::before { background: var(--green); }
|
||||
.latest::before { background: linear-gradient(90deg, var(--green), var(--blue)); }
|
||||
.experimental::before { background: var(--amber); }
|
||||
|
||||
.card:hover {
|
||||
border-color: var(--border2);
|
||||
background: var(--bg3);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.card:hover::before { opacity: 1; }
|
||||
|
||||
/* ── Header ── */
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.badges {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
font-size: 9px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
padding: 2px 8px;
|
||||
border-radius: 2px;
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
.badgeLatest {
|
||||
color: var(--green);
|
||||
border-color: var(--green-dim);
|
||||
background: rgba(57, 211, 83, 0.08);
|
||||
animation: pulseBorder 2s ease infinite;
|
||||
}
|
||||
|
||||
.badgeRelease {
|
||||
color: var(--text-dim);
|
||||
border-color: var(--border2);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.badgeExperimental {
|
||||
color: var(--amber);
|
||||
border-color: var(--amber-dim);
|
||||
background: rgba(240, 160, 48, 0.08);
|
||||
}
|
||||
|
||||
.arrow {
|
||||
font-size: 14px;
|
||||
color: var(--border2);
|
||||
transition: color 0.15s, transform 0.15s;
|
||||
}
|
||||
|
||||
.card:hover .arrow {
|
||||
color: var(--text-mid);
|
||||
transform: translateX(3px);
|
||||
}
|
||||
|
||||
/* ── Version ── */
|
||||
.version {
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
color: var(--text);
|
||||
margin-bottom: 4px;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.latest .version { color: var(--green); }
|
||||
.versionBranch { font-size: 16px; color: var(--amber); }
|
||||
|
||||
/* ── Meta ── */
|
||||
.meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
margin-top: 12px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.metaRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 10px;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
.metaKey {
|
||||
min-width: 36px;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
.metaVal {
|
||||
color: var(--text-mid);
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.hash { color: var(--blue); }
|
||||
|
||||
/* ── Animations ── */
|
||||
@keyframes fadeUp {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes pulseBorder {
|
||||
0%, 100% { border-color: var(--green-dim); }
|
||||
50% { border-color: var(--green); }
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import styles from './Footer.module.css'
|
||||
import { REPO_OWNER, REPO_NAME } from '../useDocs'
|
||||
|
||||
export default function Footer({ source }) {
|
||||
const now = new Intl.DateTimeFormat('en-GB', {
|
||||
day: '2-digit', month: 'short', year: 'numeric',
|
||||
hour: '2-digit', minute: '2-digit', timeZoneName: 'short'
|
||||
}).format(new Date())
|
||||
|
||||
return (
|
||||
<footer className={styles.footer}>
|
||||
<span>
|
||||
<span className={styles.dot} />
|
||||
Auto-generated · {source === 'manifest' ? 'manifest.json' : 'GitHub API'}
|
||||
</span>
|
||||
<span>{now}</span>
|
||||
<span>Template by:
|
||||
<a
|
||||
href={`https://github.com/somehussar/`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={styles.link}
|
||||
>
|
||||
github.com/somehussar
|
||||
</a>
|
||||
</span>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
.footer {
|
||||
margin-top: 64px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid var(--border);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
font-size: 11px;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
.dot {
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--green);
|
||||
margin-right: 6px;
|
||||
animation: blink 2s ease infinite;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: var(--blue);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.3; }
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import styles from './Section.module.css'
|
||||
|
||||
export default function Section({ label, count, variant, children }) {
|
||||
return (
|
||||
<section className={styles.section}>
|
||||
<div className={styles.header}>
|
||||
<span className={`${styles.tag} ${styles[variant]}`}>{label}</span>
|
||||
<div className={styles.line} />
|
||||
{count !== undefined && (
|
||||
<span className={styles.count}>
|
||||
{count} {label.toLowerCase()}{count !== 1 ? 's' : ''}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{children}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
.section {
|
||||
margin-top: 40px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.tag {
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.15em;
|
||||
text-transform: uppercase;
|
||||
padding: 3px 10px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.releases {
|
||||
color: var(--green);
|
||||
border-color: var(--green-dim);
|
||||
background: rgba(57, 211, 83, 0.06);
|
||||
}
|
||||
|
||||
.experimental {
|
||||
color: var(--amber);
|
||||
border-color: var(--amber-dim);
|
||||
background: rgba(240, 160, 48, 0.06);
|
||||
}
|
||||
|
||||
.line {
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: var(--border);
|
||||
}
|
||||
|
||||
.count {
|
||||
font-size: 11px;
|
||||
color: var(--text-dim);
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import styles from './States.module.css'
|
||||
|
||||
export function LoadingState() {
|
||||
return (
|
||||
<div className={styles.box}>
|
||||
<div className={styles.spinner} />
|
||||
<p className={styles.label}>Fetching documentation index...</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function ErrorState({ message }) {
|
||||
return (
|
||||
<div className={styles.box}>
|
||||
<div className={styles.errorIcon}>⚠</div>
|
||||
<p className={styles.errorMsg}>{message}</p>
|
||||
<p className={styles.errorSub}>
|
||||
Check that the gh-pages branch exists and is publicly accessible.
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function EmptyState({ label }) {
|
||||
return (
|
||||
<div className={`${styles.box} ${styles.empty}`}>
|
||||
<p className={styles.label}>No {label} found.</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
.box {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
padding: 48px;
|
||||
text-align: center;
|
||||
background: var(--bg2);
|
||||
}
|
||||
|
||||
.empty {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border: 2px solid var(--border2);
|
||||
border-top-color: var(--green);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.75s linear infinite;
|
||||
margin: 0 auto 14px;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 12px;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
.errorIcon {
|
||||
font-size: 22px;
|
||||
margin-bottom: 10px;
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
.errorMsg {
|
||||
font-size: 13px;
|
||||
color: var(--red);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.errorSub {
|
||||
font-size: 11px;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import styles from './TerminalHeader.module.css'
|
||||
import { PROJECT_NAME, REPO_NAME, LINKS, AUTHORS } from '../config'
|
||||
|
||||
function useGitHubProfile(username) {
|
||||
const [profile, setProfile] = useState(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
const cacheKey = 'gh-profile-' + username
|
||||
|
||||
// Check sessionStorage first — avoids hammering the API on every render
|
||||
try {
|
||||
const cached = sessionStorage.getItem(cacheKey)
|
||||
if (cached) {
|
||||
setProfile(JSON.parse(cached))
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
fetch('https://api.github.com/users/' + username)
|
||||
.then(r => {
|
||||
if (!r.ok) throw new Error('HTTP ' + r.status)
|
||||
return r.json()
|
||||
})
|
||||
.then(d => {
|
||||
// Don't cache error responses like rate limit messages
|
||||
if (!d.message) {
|
||||
try { sessionStorage.setItem(cacheKey, JSON.stringify(d)) } catch (e) {}
|
||||
}
|
||||
setProfile(d)
|
||||
setLoading(false)
|
||||
})
|
||||
.catch(() => setLoading(false))
|
||||
}, [username])
|
||||
|
||||
return { profile, loading }
|
||||
}
|
||||
|
||||
function AuthorTile({ username, active }) {
|
||||
const { profile, loading } = useGitHubProfile(username)
|
||||
const tileClass = styles.authorTile + (active ? ' ' + styles.authorTileActive : '')
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className={tileClass}>
|
||||
<div className={styles.authorAvatarSkeleton} />
|
||||
<div className={styles.authorInfo}>
|
||||
<div className={styles.authorSkeleton} style={{ width: '60px' }} />
|
||||
<div className={styles.authorSkeleton} style={{ width: '80px', marginTop: '4px' }} />
|
||||
<div className={styles.authorBioPlaceholder} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!profile || profile.message) {
|
||||
return (
|
||||
<a
|
||||
href={'https://github.com/' + username}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={tileClass}
|
||||
>
|
||||
<div className={styles.authorAvatarSkeleton} />
|
||||
<div className={styles.authorInfo}>
|
||||
<span className={styles.authorName}>{username}</span>
|
||||
<span className={styles.authorLogin}>@{username}</span>
|
||||
<div className={styles.authorBioPlaceholder} />
|
||||
</div>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<a
|
||||
href={profile.html_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={tileClass}
|
||||
>
|
||||
<img
|
||||
src={profile.avatar_url}
|
||||
alt={profile.login}
|
||||
className={styles.authorAvatar}
|
||||
/>
|
||||
<div className={styles.authorInfo}>
|
||||
<span className={styles.authorName}>{profile.name || profile.login}</span>
|
||||
<span className={styles.authorLogin}>@{profile.login}</span>
|
||||
{profile.bio
|
||||
? <span className={styles.authorBio}>{profile.bio}</span>
|
||||
: <div className={styles.authorBioPlaceholder} />
|
||||
}
|
||||
</div>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
function AuthorsTiles() {
|
||||
const [active, setActive] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
if (AUTHORS.length <= 1) return
|
||||
const t = setInterval(() => {
|
||||
setActive(i => (i + 1) % AUTHORS.length)
|
||||
}, 4000)
|
||||
return () => clearInterval(t)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className={styles.authors}>
|
||||
<div className={styles.outputLine}>
|
||||
<span className={styles.chevron}>›</span> Authors:
|
||||
</div>
|
||||
<div className={styles.authorRow}>
|
||||
{AUTHORS.map((username, i) => (
|
||||
<AuthorTile
|
||||
key={username}
|
||||
username={username}
|
||||
active={i === active}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function TerminalHeader() {
|
||||
return (
|
||||
<div className={styles.wrap}>
|
||||
<div className={styles.bar}>
|
||||
<div className={styles.dot + ' ' + styles.red} />
|
||||
<div className={styles.dot + ' ' + styles.amber} />
|
||||
<div className={styles.dot + ' ' + styles.green} />
|
||||
<span className={styles.title}>javadoc-index — bash</span>
|
||||
</div>
|
||||
<div className={styles.body}>
|
||||
<h1 className={styles.heading}>
|
||||
{PROJECT_NAME}
|
||||
</h1>
|
||||
|
||||
<div className={styles.promptLine}>
|
||||
<span className={styles.prompt}>$</span>
|
||||
<span className={styles.cmd}>browse-docs</span>
|
||||
<span className={styles.arg}>{REPO_NAME}</span>
|
||||
<span className={styles.flag}>--all-versions</span>
|
||||
</div>
|
||||
|
||||
<div className={styles.outputLine}>
|
||||
<span className={styles.chevron}>›</span> Fetching available documentation builds...
|
||||
</div>
|
||||
<div className={styles.outputLine}>
|
||||
<span className={styles.chevron}>›</span> Found project links:
|
||||
</div>
|
||||
|
||||
<div className={styles.links}>
|
||||
{LINKS.map(function(l) {
|
||||
return (
|
||||
<a
|
||||
key={l.tag}
|
||||
href={l.href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={styles.link}
|
||||
>
|
||||
<span className={styles.linkTag} style={{ color: l.color, borderColor: l.color }}>
|
||||
{l.tag}
|
||||
</span>
|
||||
<span className={styles.linkLabel}>{l.label}</span>
|
||||
<span className={styles.linkArrow} style={{ color: l.color }}>↗</span>
|
||||
</a>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
<AuthorsTiles />
|
||||
|
||||
<div className={styles.promptLine} style={{ marginTop: '16px' }}>
|
||||
<span className={styles.prompt}>$</span>
|
||||
<span className={styles.cursor} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
.wrap {
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
|
||||
.bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background: var(--bg3);
|
||||
border: 1px solid var(--border2);
|
||||
border-bottom: none;
|
||||
border-radius: 8px 8px 0 0;
|
||||
padding: 10px 16px;
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.red { background: #ff5f57; }
|
||||
.amber { background: #febc2e; }
|
||||
.green { background: #28c840; }
|
||||
|
||||
.title {
|
||||
margin-left: 8px;
|
||||
font-size: 11px;
|
||||
color: var(--text-dim);
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.body {
|
||||
background: var(--bg2);
|
||||
border: 1px solid var(--border2);
|
||||
border-radius: 0 0 8px 8px;
|
||||
padding: 16px 36px 36px;
|
||||
}
|
||||
|
||||
/* ── Heading ── */
|
||||
.heading {
|
||||
font-family: 'IBM Plex Sans', sans-serif;
|
||||
font-size: clamp(28px, 5vw, 52px);
|
||||
font-weight: 300;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--text);
|
||||
line-height: 1.05;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.heading strong {
|
||||
font-weight: 600;
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
/* ── Prompt ── */
|
||||
.promptLine {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 10px;
|
||||
margin-bottom: 6px;
|
||||
font-size: 13px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.prompt { color: var(--green); font-weight: 600; }
|
||||
.cmd { color: var(--text); }
|
||||
.arg { color: var(--blue); }
|
||||
.flag { color: var(--amber); }
|
||||
|
||||
/* ── Output lines ── */
|
||||
.outputLine {
|
||||
font-size: 12px;
|
||||
color: var(--text-dim);
|
||||
line-height: 1.8;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.chevron {
|
||||
color: var(--text-mid);
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
/* ── Links ── */
|
||||
.links {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
padding-left: 36px;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
text-decoration: none;
|
||||
padding: 3px 0;
|
||||
width: fit-content;
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
|
||||
.link:hover { opacity: 0.8; }
|
||||
|
||||
.link:hover .linkLabel {
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
|
||||
.linkTag {
|
||||
font-size: 9px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.1em;
|
||||
border: 1px solid;
|
||||
border-radius: 2px;
|
||||
padding: 1px 5px;
|
||||
min-width: 28px;
|
||||
text-align: center;
|
||||
opacity: 0.9;
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
}
|
||||
|
||||
.linkLabel {
|
||||
font-size: 12px;
|
||||
color: var(--text-mid);
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
}
|
||||
|
||||
.linkArrow {
|
||||
font-size: 11px;
|
||||
opacity: 0.6;
|
||||
transition: opacity 0.15s, transform 0.15s;
|
||||
}
|
||||
|
||||
.link:hover .linkArrow {
|
||||
opacity: 1;
|
||||
transform: translate(2px, -2px);
|
||||
}
|
||||
|
||||
/* ── Authors ── */
|
||||
.authors {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.authorRow {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
padding-left: 36px;
|
||||
margin-top: 6px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* ── Author tile ── */
|
||||
.authorTile {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
background: var(--bg3);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
padding: 8px 12px;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
/* width is dynamic — grows with content */
|
||||
width: max-content;
|
||||
min-width: 140px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: border-color 0.2s, background 0.2s, transform 0.2s;
|
||||
}
|
||||
|
||||
.authorTile::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0;
|
||||
height: 2px;
|
||||
background: var(--green);
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.authorTileActive::before,
|
||||
.authorTile:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.authorTileActive {
|
||||
border-color: var(--green-dim);
|
||||
background: rgba(57, 211, 83, 0.04);
|
||||
}
|
||||
|
||||
.authorTile:hover {
|
||||
border-color: var(--border2);
|
||||
background: #161b22;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* ── Author content ── */
|
||||
.authorAvatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid var(--border2);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.authorAvatarSkeleton {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
background: var(--border2);
|
||||
flex-shrink: 0;
|
||||
animation: shimmer 1.5s ease infinite;
|
||||
}
|
||||
|
||||
.authorInfo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.authorName {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--text);
|
||||
font-family: 'IBM Plex Sans', sans-serif;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.authorLogin {
|
||||
font-size: 10px;
|
||||
color: var(--blue);
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Bio — always reserves vertical height, width animates open */
|
||||
.authorBio {
|
||||
display: block;
|
||||
font-size: 10px;
|
||||
color: var(--text-dim);
|
||||
font-family: 'IBM Plex Sans', sans-serif;
|
||||
margin-top: 2px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
/* always reserve vertical space */
|
||||
height: 14px;
|
||||
/* collapsed horizontally */
|
||||
width: 0;
|
||||
opacity: 0;
|
||||
transition: opacity 0.25s ease, width 0.3s ease;
|
||||
}
|
||||
|
||||
.authorTileActive .authorBio,
|
||||
.authorTile:hover .authorBio {
|
||||
width: 200px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Placeholder for loading/no-profile/no-bio states */
|
||||
.authorBioPlaceholder {
|
||||
height: 14px;
|
||||
margin-top: 2px;
|
||||
width: 0; /* never takes horizontal space, never expands */
|
||||
}
|
||||
|
||||
.authorSkeleton {
|
||||
height: 9px;
|
||||
border-radius: 4px;
|
||||
background: var(--border2);
|
||||
animation: shimmer 1.5s ease infinite;
|
||||
}
|
||||
|
||||
/* ── Cursor ── */
|
||||
.cursor {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 13px;
|
||||
background: var(--green);
|
||||
margin-left: 2px;
|
||||
vertical-align: text-bottom;
|
||||
animation: blink 1s step-end infinite;
|
||||
}
|
||||
|
||||
/* ── Animations ── */
|
||||
@keyframes blink {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0; }
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0%, 100% { opacity: 0.4; }
|
||||
50% { opacity: 0.8; }
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.body { padding: 20px; }
|
||||
.links { padding-left: 20px; }
|
||||
.authorRow { padding-left: 20px; }
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// ─── Project Configuration ───────────────────────────────────
|
||||
// This is the ONLY file that should differ between projects.
|
||||
// Everything else in the pages app is generic.
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
export const PROJECT_NAME = 'CustomNPC+'
|
||||
export const REPO_OWNER = 'KAMKEEL'
|
||||
export const REPO_NAME = 'CustomNPC-Plus'
|
||||
|
||||
export const LINKS = [
|
||||
{
|
||||
tag: 'GH',
|
||||
label: 'github.com/KAMKEEL/CustomNPC-Plus',
|
||||
href: 'https://github.com/KAMKEEL/CustomNPC-Plus',
|
||||
color: '#e6edf3',
|
||||
},
|
||||
{
|
||||
tag: 'DC',
|
||||
label: 'discord.gg/94qmjEDxkp',
|
||||
href: 'https://discord.gg/94qmjEDxkp',
|
||||
color: '#5865f2',
|
||||
},
|
||||
{
|
||||
tag: 'CF',
|
||||
label: 'curseforge.com/minecraft/mc-mods/customnpc-plus',
|
||||
href: 'https://www.curseforge.com/minecraft/mc-mods/customnpc-plus',
|
||||
color: '#f16436',
|
||||
},
|
||||
{
|
||||
tag: 'MR',
|
||||
label: 'modrinth.com/mod/customnpc-plus',
|
||||
href: 'https://modrinth.com/mod/customnpc-plus/',
|
||||
color: '#1bd96a',
|
||||
},
|
||||
]
|
||||
|
||||
// GitHub usernames — profiles are fetched at runtime
|
||||
export const AUTHORS = ['kamkeel', 'c0508383', 'bigguy345', 'somehussar']
|
||||
@@ -0,0 +1,50 @@
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--bg: #080b0f;
|
||||
--bg2: #0d1117;
|
||||
--bg3: #131920;
|
||||
--border: #1e2936;
|
||||
--border2: #253040;
|
||||
--green: #39d353;
|
||||
--green-dim: #1a6b2a;
|
||||
--amber: #f0a030;
|
||||
--amber-dim: #7a4e10;
|
||||
--blue: #58a6ff;
|
||||
--blue-dim: #1a3a6b;
|
||||
--text: #cdd9e5;
|
||||
--text-dim: #636e7b;
|
||||
--text-mid: #8b949e;
|
||||
--red: #f85149;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* Scanlines */
|
||||
body::after {
|
||||
content: '';
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: repeating-linear-gradient(
|
||||
0deg,
|
||||
transparent,
|
||||
transparent 2px,
|
||||
rgba(0,0,0,0.025) 2px,
|
||||
rgba(0,0,0,0.025) 4px
|
||||
);
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
a { color: inherit; text-decoration: none; }
|
||||
|
||||
::-webkit-scrollbar { width: 6px; }
|
||||
::-webkit-scrollbar-track { background: var(--bg2); }
|
||||
::-webkit-scrollbar-thumb { background: var(--border2); border-radius: 3px; }
|
||||
::-webkit-scrollbar-thumb:hover { background: var(--text-dim); }
|
||||
@@ -0,0 +1,13 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App'
|
||||
import { PROJECT_NAME } from './config'
|
||||
import './index.css'
|
||||
|
||||
document.title = `${PROJECT_NAME} — Javadoc Index`
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
)
|
||||
@@ -0,0 +1,94 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { REPO_OWNER, REPO_NAME } from './config'
|
||||
|
||||
const BASE_PATH = `/${REPO_NAME}`
|
||||
const API_BASE = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}`
|
||||
|
||||
// Cache bust on every page load — appends current timestamp to manifest URL
|
||||
// so the browser never serves a stale manifest.json
|
||||
async function fetchFromManifest() {
|
||||
const bust = `?t=${Date.now()}`
|
||||
const res = await fetch(`${BASE_PATH}/manifest.json${bust}`, {
|
||||
cache: 'no-store', // tells browser not to cache this request at all
|
||||
})
|
||||
if (!res.ok) throw new Error('No manifest')
|
||||
return res.json()
|
||||
}
|
||||
|
||||
async function fetchFromGitHubAPI() {
|
||||
const refRes = await fetch(`${API_BASE}/git/ref/heads/gh-pages`)
|
||||
if (!refRes.ok) throw new Error('gh-pages branch not found')
|
||||
const refData = await refRes.json()
|
||||
const sha = refData.object.sha
|
||||
|
||||
const treeRes = await fetch(`${API_BASE}/git/trees/${sha}?recursive=1`)
|
||||
if (!treeRes.ok) throw new Error('Could not fetch tree')
|
||||
const treeData = await treeRes.json()
|
||||
|
||||
const commitsRes = await fetch(`${API_BASE}/commits?sha=gh-pages&per_page=100`)
|
||||
const commits = commitsRes.ok ? await commitsRes.json() : []
|
||||
|
||||
const commitMap = {}
|
||||
commits.forEach(c => {
|
||||
const date = c.commit?.committer?.date || c.commit?.author?.date || ''
|
||||
const hash = c.sha?.slice(0, 7) || ''
|
||||
const msg = c.commit?.message || ''
|
||||
const vMatch = msg.match(/releases\/([\d.]+)/)
|
||||
if (vMatch) commitMap[`releases/${vMatch[1]}`] = { date, hash }
|
||||
const eMatch = msg.match(/experimental\/([\w-]+)/)
|
||||
if (eMatch) commitMap[`experimental/${eMatch[1]}`] = { date, hash }
|
||||
})
|
||||
|
||||
const releaseDirs = new Set()
|
||||
const experimentalDirs = new Set()
|
||||
|
||||
treeData.tree.forEach(item => {
|
||||
if (item.type !== 'tree') return
|
||||
const parts = item.path.split('/')
|
||||
if (parts[0] === 'releases' && parts.length === 2) releaseDirs.add(parts[1])
|
||||
if (parts[0] === 'experimental' && parts.length === 2) experimentalDirs.add(parts[1])
|
||||
})
|
||||
|
||||
// Sort releases newest first, mark first as latest
|
||||
const sortedReleases = [...releaseDirs]
|
||||
.sort((a, b) => b.localeCompare(a, undefined, { numeric: true }))
|
||||
|
||||
const releases = sortedReleases.map((v, i) => {
|
||||
const c = commitMap[`releases/${v}`] || {}
|
||||
return {
|
||||
id: v,
|
||||
version: v,
|
||||
path: `releases/${v}`,
|
||||
date: c.date,
|
||||
hash: c.hash,
|
||||
isLatest: i === 0, // newest version is always latest
|
||||
}
|
||||
})
|
||||
|
||||
const experimental = [...experimentalDirs].sort().map(b => {
|
||||
const c = commitMap[`experimental/${b}`] || {}
|
||||
return { id: b, branch: b, path: `experimental/${b}`, date: c.date, hash: c.hash }
|
||||
})
|
||||
|
||||
return { releases, experimental, source: 'api' }
|
||||
}
|
||||
|
||||
export function useDocs() {
|
||||
const [data, setData] = useState(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
fetchFromManifest()
|
||||
.then(d => { setData(d); setLoading(false) })
|
||||
.catch(() =>
|
||||
fetchFromGitHubAPI()
|
||||
.then(d => { setData(d); setLoading(false) })
|
||||
.catch(e => { setError(e.message); setLoading(false) })
|
||||
)
|
||||
}, [])
|
||||
|
||||
return { data, loading, error }
|
||||
}
|
||||
|
||||
export { BASE_PATH, REPO_OWNER, REPO_NAME }
|
||||
@@ -0,0 +1,12 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import { REPO_NAME } from './src/config.js'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
base: `/${REPO_NAME}/`,
|
||||
build: {
|
||||
outDir: '../../.github/pages-dist',
|
||||
emptyOutDir: true,
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
# -----------------------------------------------
|
||||
# doc-config.sh — shared config for javadoc deploy
|
||||
#
|
||||
# Sourced by:
|
||||
# - resolve-doc-path.sh
|
||||
# - javadoc.yml (via sanity-check job)
|
||||
#
|
||||
# ADD WHITELISTED BRANCHES HERE AND NOWHERE ELSE.
|
||||
# main is always handled automatically.
|
||||
# -----------------------------------------------
|
||||
|
||||
MAIN_BRANCH_NAME="master"
|
||||
|
||||
EXPERIMENTAL_WHITELIST=(
|
||||
"dev"
|
||||
# "new-branch"
|
||||
)
|
||||
@@ -0,0 +1,107 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
RELEASES=()
|
||||
EXPERIMENTAL=()
|
||||
|
||||
# Collect releases
|
||||
if [ -d "./releases" ]; then
|
||||
for dir in ./releases/*/; do
|
||||
[ -d "$dir" ] || continue
|
||||
name=$(basename "$dir")
|
||||
RELEASES+=("$name")
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect experimental branches
|
||||
if [ -d "./experimental" ]; then
|
||||
for dir in ./experimental/*/; do
|
||||
[ -d "$dir" ] || continue
|
||||
name=$(basename "$dir")
|
||||
EXPERIMENTAL+=("$name")
|
||||
done
|
||||
fi
|
||||
|
||||
# Sort releases in reverse semver order (newest first)
|
||||
if [ ${#RELEASES[@]} -gt 0 ]; then
|
||||
IFS=$'\n' RELEASES=($(sort -rV <<< "${RELEASES[*]}")); unset IFS
|
||||
fi
|
||||
|
||||
# ── Store source commit metadata for this deploy ───────────────
|
||||
# DEPLOY_PATH, SOURCE_HASH, SOURCE_DATE are passed in as env vars from the workflow
|
||||
if [ -n "$DEPLOY_PATH" ] && [ -n "$SOURCE_HASH" ]; then
|
||||
mkdir -p ".doc-meta"
|
||||
META_KEY="${DEPLOY_PATH//\//_}"
|
||||
cat > ".doc-meta/${META_KEY}.json" << JSON
|
||||
{"hash":"${SOURCE_HASH}","date":"${SOURCE_DATE}"}
|
||||
JSON
|
||||
echo "Stored source metadata for $DEPLOY_PATH → $SOURCE_HASH @ $SOURCE_DATE"
|
||||
fi
|
||||
|
||||
# ── Helper: get source commit hash + date for a path ──────────
|
||||
get_commit_info() {
|
||||
local path="$1"
|
||||
|
||||
# If this is the path we just deployed, use the injected source commit
|
||||
if [ "$path" = "$DEPLOY_PATH" ] && [ -n "$SOURCE_HASH" ]; then
|
||||
echo "${SOURCE_HASH} ${SOURCE_DATE}"
|
||||
return
|
||||
fi
|
||||
|
||||
# Try reading from stored metadata file (set by previous deploys)
|
||||
local meta=".doc-meta/${path//\//_}.json"
|
||||
if [ -f "$meta" ]; then
|
||||
local hash date
|
||||
hash=$(python3 -c "import json,sys; d=json.load(open('$meta')); print(d.get('hash',''))" 2>/dev/null || grep -o '"hash":"[^"]*"' "$meta" | cut -d'"' -f4)
|
||||
date=$(python3 -c "import json,sys; d=json.load(open('$meta')); print(d.get('date',''))" 2>/dev/null || grep -o '"date":"[^"]*"' "$meta" | cut -d'"' -f4)
|
||||
echo "${hash:-} ${date:-}"
|
||||
return
|
||||
fi
|
||||
|
||||
# Last resort: gh-pages git log (hash will be wrong but better than empty)
|
||||
local hash date
|
||||
hash=$(git log --oneline -1 -- "$path" 2>/dev/null | awk '{print $1}')
|
||||
date=$(git log --format="%cI" -1 -- "$path" 2>/dev/null)
|
||||
echo "${hash:-} ${date:-}"
|
||||
}
|
||||
|
||||
# ── Build releases JSON ────────────────────────────────────────
|
||||
RELEASES_JSON="["
|
||||
|
||||
for i in "${!RELEASES[@]}"; do
|
||||
version="${RELEASES[$i]}"
|
||||
read -r HASH DATE <<< "$(get_commit_info "releases/$version")"
|
||||
if [ "$i" -eq 0 ]; then
|
||||
IS_LATEST="true"
|
||||
else
|
||||
IS_LATEST="false"
|
||||
fi
|
||||
RELEASES_JSON+="{\"id\":\"${version}\",\"version\":\"${version}\",\"path\":\"releases/${version}\",\"date\":\"${DATE}\",\"isLatest\":${IS_LATEST}},"
|
||||
done
|
||||
|
||||
RELEASES_JSON="${RELEASES_JSON%,}]"
|
||||
|
||||
# ── Build experimental JSON ────────────────────────────────────
|
||||
EXPERIMENTAL_JSON="["
|
||||
|
||||
for branch in "${EXPERIMENTAL[@]}"; do
|
||||
read -r HASH DATE <<< "$(get_commit_info "experimental/$branch")"
|
||||
EXPERIMENTAL_JSON+="{\"id\":\"${branch}\",\"branch\":\"${branch}\",\"path\":\"experimental/${branch}\",\"hash\":\"${HASH}\",\"date\":\"${DATE}\"},"
|
||||
done
|
||||
|
||||
EXPERIMENTAL_JSON="${EXPERIMENTAL_JSON%,}]"
|
||||
|
||||
# ── Write manifest.json ────────────────────────────────────────
|
||||
cat > manifest.json << JSON
|
||||
{
|
||||
"releases": ${RELEASES_JSON},
|
||||
"experimental": ${EXPERIMENTAL_JSON},
|
||||
"generated": "$(date -u '+%Y-%m-%dT%H:%M:%SZ')",
|
||||
"source": "manifest"
|
||||
}
|
||||
JSON
|
||||
|
||||
echo "manifest.json generated:"
|
||||
cat manifest.json
|
||||
echo ""
|
||||
echo "Done."
|
||||
@@ -0,0 +1,52 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "$SCRIPT_DIR/doc-config.sh"
|
||||
|
||||
BRANCH="${GITHUB_REF#refs/heads/}"
|
||||
SAFE_BRANCH="${BRANCH//\//-}"
|
||||
|
||||
if [ "$BRANCH" = "$MAIN_BRANCH_NAME" ]; then
|
||||
# Version resolution order:
|
||||
# -> check if gradle.properties has a `modVersion` tag. If it does, just grab it.
|
||||
# -> check if it has the `version` tag. Grab it if it does
|
||||
# -> check build.gradle for the version tag.
|
||||
if [ -f "gradle.properties" ] && grep -q "^modVersion=" gradle.properties; then
|
||||
VERSION=$(grep "^modVersion=" gradle.properties | cut -d'=' -f2 | tr -d '[:space:]')
|
||||
elif [ -f "gradle.properties" ] && grep -q "^version=" gradle.properties; then
|
||||
VERSION=$(grep "^version=" gradle.properties | cut -d'=' -f2 | tr -d '[:space:]')
|
||||
elif [ -f "build.gradle" ]; then
|
||||
VERSION=$(grep "^version" build.gradle | head -1 | sed "s/version\s*=\s*['\"]//;s/['\"].*//;s/\s//g")
|
||||
elif [ -f "build.gradle.kts" ]; then
|
||||
VERSION=$(grep "^version" build.gradle.kts | head -1 | sed "s/version\s*=\s*\"//;s/\".*//;s/\s//g")
|
||||
else
|
||||
echo "Could not find version. Defaulting to 'unknown'."
|
||||
VERSION="unknown"
|
||||
fi
|
||||
|
||||
echo "Resolved version: $VERSION"
|
||||
|
||||
# Only deploy to releases/<version> — isLatest is handled by generate-index.sh
|
||||
echo "path=releases/$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "extra_path=" >> $GITHUB_OUTPUT
|
||||
|
||||
else
|
||||
MATCHED=false
|
||||
for ALLOWED in "${EXPERIMENTAL_WHITELIST[@]}"; do
|
||||
if [ "$BRANCH" = "$ALLOWED" ]; then
|
||||
MATCHED=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$MATCHED" = true ]; then
|
||||
echo "path=experimental/$SAFE_BRANCH" >> $GITHUB_OUTPUT
|
||||
echo "extra_path=" >> $GITHUB_OUTPUT
|
||||
echo "Branch '$BRANCH' is whitelisted, deploying to experimental/$SAFE_BRANCH"
|
||||
else
|
||||
echo "path=" >> $GITHUB_OUTPUT
|
||||
echo "extra_path=" >> $GITHUB_OUTPUT
|
||||
echo "Branch '$BRANCH' is not whitelisted. Skipping deploy."
|
||||
fi
|
||||
fi
|
||||
@@ -24,8 +24,8 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Initialize CNPC+ API submodule
|
||||
run: git submodule update --init --recursive src/api
|
||||
- name: Initialize submodules
|
||||
run: git submodule update --init --recursive
|
||||
|
||||
- name: Checkout workflows repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
name: Generate and Deploy Javadoc
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- '**'
|
||||
delete:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
force_deploy:
|
||||
description: 'Force redeploy Javadocs without a new commit'
|
||||
required: false
|
||||
default: 'true'
|
||||
|
||||
jobs:
|
||||
sanity-check:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
should_deploy: ${{ steps.deploy_check.outputs.should_deploy }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Check for deploy tag
|
||||
id: deploy_check
|
||||
run: |
|
||||
source .github/scripts/doc-config.sh
|
||||
|
||||
BRANCH="${GITHUB_REF#refs/heads/}"
|
||||
|
||||
# Always deploy on manual trigger
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
echo "should_deploy=true" >> $GITHUB_OUTPUT
|
||||
echo "Reason: workflow_dispatch"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Main branch: only deploy if commit message contains [deploy-docs]
|
||||
if [ "$BRANCH" = "$MAIN_BRANCH_NAME" ]; then
|
||||
if echo "${{ github.event.head_commit.message }}" | grep -q "\[deploy-docs\]"; then
|
||||
echo "should_deploy=true" >> $GITHUB_OUTPUT
|
||||
echo "Reason: master branch with [deploy-docs] tag"
|
||||
else
|
||||
echo "should_deploy=false" >> $GITHUB_OUTPUT
|
||||
echo "Reason: master branch without [deploy-docs] tag, skipping"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Experimental branches: only deploy if whitelisted
|
||||
for ALLOWED in "${EXPERIMENTAL_WHITELIST[@]}"; do
|
||||
if [ "$BRANCH" = "$ALLOWED" ]; then
|
||||
echo "should_deploy=true" >> $GITHUB_OUTPUT
|
||||
echo "Reason: branch '$BRANCH' is whitelisted"
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
|
||||
echo "should_deploy=false" >> $GITHUB_OUTPUT
|
||||
echo "Reason: branch '$BRANCH' is not whitelisted, skipping"
|
||||
|
||||
build-and-deploy-javadoc:
|
||||
needs: sanity-check
|
||||
if: needs.sanity-check.outputs.should_deploy == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout source branch
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Initialize submodules
|
||||
run: git submodule update --init --recursive
|
||||
|
||||
- name: Setup JDK 8 (compile)
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '8'
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Set up JDK 8 (Zulu)
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '8'
|
||||
distribution: 'zulu'
|
||||
- name: Setup JDK 11 (javadoc)
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '11'
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Extract convention plugin version
|
||||
id: convention
|
||||
run: |
|
||||
VERSION=$(grep "id 'com.gtnewhorizons.gtnhsettingsconvention'" settings.gradle settings.gradle.kts 2>/dev/null | grep -o "version '[0-9]\+\.[0-9]\+\.[0-9]\+'" | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+')
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Detected convention version: $VERSION"
|
||||
|
||||
- name: Get source commit info
|
||||
id: source_commit
|
||||
run: |
|
||||
echo "hash=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||
echo "date=$(git log -1 --format='%cI')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache Gradle wrapper
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.gradle/wrapper
|
||||
key: gradle-wrapper-${{ hashFiles('**/gradle-wrapper.properties') }}
|
||||
|
||||
- name: Cache Gradle dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/caches/modules-*
|
||||
~/.gradle/caches/jars-*
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', '**/gradle.properties') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
|
||||
- name: Cache RFG
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
build/rfg
|
||||
build/tmp/.cache/expanded
|
||||
build/tmp/deobfuscateMergedJarToSrg
|
||||
key: ${{ runner.os }}-rfg-convention${{ steps.convention.outputs.version }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-rfg-
|
||||
|
||||
- name: Cache Node modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .github/pages/node_modules
|
||||
key: ${{ runner.os }}-node-${{ hashFiles('.github/pages/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- name: Generate Javadoc
|
||||
run: ./gradlew javadoc
|
||||
|
||||
- name: Get deploy path
|
||||
id: deploy_path
|
||||
run: bash .github/scripts/resolve-doc-path.sh
|
||||
|
||||
- name: Deploy to gh-pages
|
||||
if: steps.deploy_path.outputs.path != ''
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./build/docs/javadoc
|
||||
destination_dir: ${{ steps.deploy_path.outputs.path }}
|
||||
keep_files: true
|
||||
|
||||
# Give GitHub a moment to settle after the peaceiris push
|
||||
- name: Wait for gh-pages to settle
|
||||
run: sleep 10
|
||||
|
||||
- name: Checkout gh-pages
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: gh-pages
|
||||
path: gh-pages-index
|
||||
|
||||
- name: Generate manifest
|
||||
run: |
|
||||
cd gh-pages-index
|
||||
SOURCE_HASH="${{ steps.source_commit.outputs.hash }}" \
|
||||
SOURCE_DATE="${{ steps.source_commit.outputs.date }}" \
|
||||
DEPLOY_PATH="${{ steps.deploy_path.outputs.path }}" \
|
||||
bash ../.github/scripts/generate-index.sh
|
||||
|
||||
- name: Install page dependencies
|
||||
run: npm ci --prefix .github/pages
|
||||
|
||||
- name: Build index page
|
||||
run: |
|
||||
mkdir -p .github/pages/public
|
||||
cp gh-pages-index/manifest.json .github/pages/public/manifest.json
|
||||
npm run build --prefix .github/pages
|
||||
|
||||
- name: Deploy index to gh-pages
|
||||
run: |
|
||||
cp -r .github/pages-dist/* gh-pages-index/
|
||||
cd gh-pages-index
|
||||
git config user.name "github-actions"
|
||||
git config user.email "github-actions@github.com"
|
||||
git add -A
|
||||
git commit -m "Regenerate index and manifest [skip ci]" || echo "Unchanged"
|
||||
git push
|
||||
|
||||
cleanup-on-delete:
|
||||
if: github.event_name == 'delete' && github.event.ref_type == 'branch'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout gh-pages
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: 'gh-pages'
|
||||
|
||||
- name: Remove deleted branch docs
|
||||
run: |
|
||||
BRANCH="${{ github.event.ref }}"
|
||||
SAFE="experimental/${BRANCH//\//-}"
|
||||
if [ -d "./$SAFE" ]; then
|
||||
rm -rf "./$SAFE"
|
||||
META_KEY="${SAFE//\//_}"
|
||||
rm -f ".doc-meta/${META_KEY}.json"
|
||||
git config user.name "github-actions"
|
||||
git config user.email "github-actions@github.com"
|
||||
git add -A
|
||||
git commit -m "Remove docs for deleted branch: $BRANCH"
|
||||
git push
|
||||
else
|
||||
echo "No docs found for $SAFE, skipping."
|
||||
fi
|
||||
@@ -45,4 +45,6 @@ src/main/resources/assets/customnpcs/api
|
||||
df/
|
||||
tmp/
|
||||
src/test
|
||||
node_modules
|
||||
.github/pages-dist
|
||||
|
||||
+24
-3
@@ -6,7 +6,7 @@ plugins {
|
||||
id "dts.typescript-generator"
|
||||
}
|
||||
|
||||
version = "1.11-prerelease3"
|
||||
version = "1.11"
|
||||
group= "kamkeel.customnpc-plus"
|
||||
archivesBaseName = "CustomNPC-Plus"
|
||||
|
||||
@@ -81,11 +81,32 @@ processResources.dependsOn generateTypeScriptDefinitions
|
||||
sourcesJar.dependsOn generateTypeScriptDefinitions
|
||||
|
||||
|
||||
// Register @hookName as a valid Javadoc tag
|
||||
tasks.withType(Javadoc) {
|
||||
// Javadoc configuration
|
||||
javadoc {
|
||||
options.encoding = 'UTF-8'
|
||||
options.charSet = 'UTF-8'
|
||||
options.docEncoding = 'UTF-8'
|
||||
|
||||
options.tags = [
|
||||
'hookName:a:Hook Name:'
|
||||
]
|
||||
|
||||
options.stylesheetFile = file('.github/javadoc/javadoc-theme.css')
|
||||
options.header = file('.github/javadoc/javadoc-header.html').text.replaceAll('\\s+', ' ').trim()
|
||||
|
||||
options.addBooleanOption('-allow-script-in-comments', true)
|
||||
options.footer = file('.github/javadoc/javadoc-footer.html').text.replaceAll('\\s+', ' ').trim()
|
||||
|
||||
// Only document the public API, not net.minecraft stubs
|
||||
include 'noppes/npcs/api/**'
|
||||
|
||||
options.links(
|
||||
'https://docs.oracle.com/en/java/javase/11/docs/api/'
|
||||
)
|
||||
|
||||
javadocTool = javaToolchains.javadocToolFor {
|
||||
languageVersion = JavaLanguageVersion.of(11)
|
||||
}
|
||||
}
|
||||
|
||||
// Modify the existing 'build' task to depend on 'updateAPI'
|
||||
|
||||
+1
-1
Submodule src/api updated: efce1a99b7...f91e17ab3e.
@@ -63,9 +63,9 @@ public class RenderEnergyBeam extends RenderEnergy {
|
||||
double renderOriginY = y - headOffsetY;
|
||||
double renderOriginZ = z - headOffsetZ;
|
||||
|
||||
// Get beam properties
|
||||
float beamWidth = beam.getBeamWidth();
|
||||
float headSize = beam.getHeadSize();
|
||||
// Get beam properties (interpolated for smooth size transitions)
|
||||
float beamWidth = beam.getInterpolatedBeamWidth(partialTicks);
|
||||
float headSize = beam.getInterpolatedHeadSize(partialTicks);
|
||||
int innerColor = beam.getInnerColor();
|
||||
int outerColor = beam.getOuterColor();
|
||||
float outerAlpha = beam.getOuterColorAlpha() * proximityAlpha;
|
||||
@@ -105,7 +105,7 @@ public class RenderEnergyBeam extends RenderEnergy {
|
||||
* Grows from 0 to headSize based on charge progress.
|
||||
*/
|
||||
private void renderChargingOrb(EntityAbilityBeam beam, double x, double y, double z, float partialTicks, float proximityAlpha) {
|
||||
float headSize = beam.getHeadSize();
|
||||
float headSize = beam.getInterpolatedHeadSize(partialTicks);
|
||||
float chargeProgress = beam.getInterpolatedChargeProgress(partialTicks);
|
||||
float size = headSize * chargeProgress;
|
||||
|
||||
|
||||
@@ -31,8 +31,8 @@ public class RenderEnergyDisc extends RenderEnergy {
|
||||
|
||||
// Get interpolated values
|
||||
float size = disc.getInterpolatedSize(partialTicks);
|
||||
float radius = disc.getDiscRadius() * size;
|
||||
float thickness = disc.getDiscThickness() * size;
|
||||
float radius = disc.getInterpolatedDiscRadius(partialTicks) * size;
|
||||
float thickness = disc.getInterpolatedDiscThickness(partialTicks) * size;
|
||||
|
||||
// Subtle pulsing effect
|
||||
float pulseTime = entity.ticksExisted + partialTicks;
|
||||
|
||||
@@ -43,7 +43,7 @@ public class RenderEnergyLaser extends RenderEnergy {
|
||||
double endX = laser.getInterpolatedEndX(partialTicks);
|
||||
double endY = laser.getInterpolatedEndY(partialTicks);
|
||||
double endZ = laser.getInterpolatedEndZ(partialTicks);
|
||||
float width = laser.getLaserWidth();
|
||||
float width = laser.getInterpolatedLaserWidth(partialTicks);
|
||||
float alpha = 1.0f;
|
||||
|
||||
// Calculate render offset (entity position is used as reference)
|
||||
@@ -156,7 +156,7 @@ public class RenderEnergyLaser extends RenderEnergy {
|
||||
}
|
||||
|
||||
private void renderChargingOrb(EntityAbilityLaser laser, double x, double y, double z, float partialTicks, float proximityAlpha) {
|
||||
float headSize = laser.getLaserWidth() * 0.5f;
|
||||
float headSize = laser.getInterpolatedLaserWidth(partialTicks) * 0.5f;
|
||||
float chargeProgress = laser.getInterpolatedChargeProgress(partialTicks);
|
||||
float size = headSize * chargeProgress;
|
||||
|
||||
|
||||
@@ -41,6 +41,8 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
|
||||
// Beam shape properties
|
||||
private float beamWidth = 0.3f;
|
||||
private float headSize = 0.5f;
|
||||
private float prevBeamWidth = 0.3f;
|
||||
private float prevHeadSize = 0.5f;
|
||||
|
||||
// Trail - list of points showing beam path (RELATIVE to origin!)
|
||||
private List<Vec3> trailPoints = new ArrayList<>();
|
||||
@@ -240,10 +242,12 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
|
||||
return;
|
||||
}
|
||||
|
||||
// Store previous head offset for interpolation
|
||||
// Store previous values for interpolation
|
||||
prevHeadOffsetX = headOffsetX;
|
||||
prevHeadOffsetY = headOffsetY;
|
||||
prevHeadOffsetZ = headOffsetZ;
|
||||
prevBeamWidth = beamWidth;
|
||||
prevHeadSize = headSize;
|
||||
|
||||
// Age trail points for fading effect
|
||||
if (isFadingMode()) {
|
||||
@@ -580,7 +584,12 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
|
||||
return beamWidth;
|
||||
}
|
||||
|
||||
public float getInterpolatedBeamWidth(float partialTicks) {
|
||||
return prevBeamWidth + (beamWidth - prevBeamWidth) * partialTicks;
|
||||
}
|
||||
|
||||
public void setBeamWidth(float beamWidth) {
|
||||
this.prevBeamWidth = this.beamWidth;
|
||||
this.beamWidth = beamWidth;
|
||||
}
|
||||
|
||||
@@ -588,7 +597,12 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
|
||||
return headSize;
|
||||
}
|
||||
|
||||
public float getInterpolatedHeadSize(float partialTicks) {
|
||||
return prevHeadSize + (headSize - prevHeadSize) * partialTicks;
|
||||
}
|
||||
|
||||
public void setHeadSize(float headSize) {
|
||||
this.prevHeadSize = this.headSize;
|
||||
this.headSize = headSize;
|
||||
}
|
||||
|
||||
@@ -747,6 +761,8 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
|
||||
|
||||
@Override
|
||||
protected void applyProjectileClientSyncData(NBTTagCompound nbt) {
|
||||
prevBeamWidth = beamWidth;
|
||||
prevHeadSize = headSize;
|
||||
beamWidth = nbt.getFloat("BeamWidth");
|
||||
headSize = nbt.getFloat("HeadSize");
|
||||
setAttachedToOwner(nbt.getBoolean("AttachedToOwner"));
|
||||
@@ -758,6 +774,8 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
|
||||
protected void readProjectileNBT(NBTTagCompound nbt) {
|
||||
this.beamWidth = sanitize(nbt.hasKey("BeamWidth") ? nbt.getFloat("BeamWidth") : 0.3f, 0.3f, MAX_ENTITY_SIZE);
|
||||
this.headSize = sanitize(nbt.hasKey("HeadSize") ? nbt.getFloat("HeadSize") : 0.5f, 0.5f, MAX_ENTITY_SIZE);
|
||||
this.prevBeamWidth = this.beamWidth;
|
||||
this.prevHeadSize = this.headSize;
|
||||
this.headOffsetX = nbt.hasKey("HeadOffsetX") ? nbt.getDouble("HeadOffsetX") : 0;
|
||||
this.headOffsetY = nbt.hasKey("HeadOffsetY") ? nbt.getDouble("HeadOffsetY") : 0;
|
||||
this.headOffsetZ = nbt.hasKey("HeadOffsetZ") ? nbt.getDouble("HeadOffsetZ") : 0;
|
||||
|
||||
@@ -33,6 +33,8 @@ public class EntityAbilityDisc extends EntityEnergyProjectile {
|
||||
// Disc shape properties
|
||||
private float discRadius = 1.0f; // Width of disc
|
||||
private float discThickness = 0.2f; // Height of disc
|
||||
private float prevDiscRadius = 1.0f;
|
||||
private float prevDiscThickness = 0.2f;
|
||||
|
||||
// Boomerang owner-gone tracking
|
||||
private int returnOwnerNullTicks = 0;
|
||||
@@ -134,6 +136,10 @@ public class EntityAbilityDisc extends EntityEnergyProjectile {
|
||||
|
||||
@Override
|
||||
protected void updateProjectile() {
|
||||
// Store previous values for interpolation
|
||||
prevDiscRadius = discRadius;
|
||||
prevDiscThickness = discThickness;
|
||||
|
||||
// Handle charging state (windup phase)
|
||||
if (isCharging()) {
|
||||
updateCharging();
|
||||
@@ -347,7 +353,12 @@ public class EntityAbilityDisc extends EntityEnergyProjectile {
|
||||
return discRadius;
|
||||
}
|
||||
|
||||
public float getInterpolatedDiscRadius(float partialTicks) {
|
||||
return prevDiscRadius + (discRadius - prevDiscRadius) * partialTicks;
|
||||
}
|
||||
|
||||
public void setDiscRadius(float radius) {
|
||||
this.prevDiscRadius = this.discRadius;
|
||||
this.discRadius = radius;
|
||||
}
|
||||
|
||||
@@ -355,7 +366,12 @@ public class EntityAbilityDisc extends EntityEnergyProjectile {
|
||||
return discThickness;
|
||||
}
|
||||
|
||||
public float getInterpolatedDiscThickness(float partialTicks) {
|
||||
return prevDiscThickness + (discThickness - prevDiscThickness) * partialTicks;
|
||||
}
|
||||
|
||||
public void setDiscThickness(float thickness) {
|
||||
this.prevDiscThickness = this.discThickness;
|
||||
this.discThickness = thickness;
|
||||
}
|
||||
|
||||
@@ -443,6 +459,8 @@ public class EntityAbilityDisc extends EntityEnergyProjectile {
|
||||
|
||||
@Override
|
||||
protected void applyProjectileClientSyncData(NBTTagCompound nbt) {
|
||||
prevDiscRadius = discRadius;
|
||||
prevDiscThickness = discThickness;
|
||||
discRadius = nbt.getFloat("DiscRadius");
|
||||
discThickness = nbt.getFloat("DiscThickness");
|
||||
vertical = nbt.getBoolean("Vertical");
|
||||
@@ -456,6 +474,8 @@ public class EntityAbilityDisc extends EntityEnergyProjectile {
|
||||
this.boomerangDelay = nbt.hasKey("BoomerangDelay") ? nbt.getInteger("BoomerangDelay") : 40;
|
||||
this.discRadius = sanitize(nbt.hasKey("DiscRadius") ? nbt.getFloat("DiscRadius") : 1.0f, 1.0f, MAX_ENTITY_SIZE);
|
||||
this.discThickness = sanitize(nbt.hasKey("DiscThickness") ? nbt.getFloat("DiscThickness") : 0.2f, 0.2f, MAX_ENTITY_SIZE);
|
||||
this.prevDiscRadius = this.discRadius;
|
||||
this.prevDiscThickness = this.discThickness;
|
||||
this.vertical = nbt.hasKey("Vertical") ? nbt.getBoolean("Vertical") : false;
|
||||
this.returning = nbt.hasKey("Returning") && nbt.getBoolean("Returning");
|
||||
// Charging state (common fields handled by base)
|
||||
|
||||
@@ -33,6 +33,7 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
|
||||
|
||||
// Laser-specific properties
|
||||
private float laserWidth = 0.2f;
|
||||
private float prevLaserWidth = 0.2f;
|
||||
private float expansionSpeed = 2.0f; // Blocks per tick
|
||||
private float maxLength = 32.0f; // Maximum beam reach in blocks
|
||||
|
||||
@@ -144,6 +145,9 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
|
||||
|
||||
@Override
|
||||
protected void updateProjectile() {
|
||||
// Store previous values for interpolation
|
||||
prevLaserWidth = laserWidth;
|
||||
|
||||
if (isCharging()) {
|
||||
updateCharging();
|
||||
return;
|
||||
@@ -703,7 +707,12 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
|
||||
return laserWidth;
|
||||
}
|
||||
|
||||
public float getInterpolatedLaserWidth(float partialTicks) {
|
||||
return prevLaserWidth + (laserWidth - prevLaserWidth) * partialTicks;
|
||||
}
|
||||
|
||||
public void setLaserWidth(float width) {
|
||||
this.prevLaserWidth = this.laserWidth;
|
||||
this.laserWidth = width;
|
||||
}
|
||||
|
||||
@@ -879,6 +888,7 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
|
||||
|
||||
@Override
|
||||
protected void applyProjectileClientSyncData(NBTTagCompound nbt) {
|
||||
prevLaserWidth = laserWidth;
|
||||
laserWidth = nbt.getFloat("LaserWidth");
|
||||
expansionSpeed = nbt.getFloat("ExpansionSpeed");
|
||||
maxLength = nbt.getFloat("MaxLength");
|
||||
@@ -892,6 +902,7 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
|
||||
@Override
|
||||
protected void readProjectileNBT(NBTTagCompound nbt) {
|
||||
this.laserWidth = sanitize(nbt.hasKey("LaserWidth") ? nbt.getFloat("LaserWidth") : 0.2f, 0.2f, MAX_ENTITY_SIZE);
|
||||
this.prevLaserWidth = this.laserWidth;
|
||||
this.expansionSpeed = sanitize(nbt.hasKey("ExpansionSpeed") ? nbt.getFloat("ExpansionSpeed") : 2.0f, 0.1f, MAX_ENTITY_SIZE);
|
||||
this.maxLength = sanitize(nbt.hasKey("MaxLength") ? nbt.getFloat("MaxLength") : 32.0f, 1.0f, MAX_ENTITY_SIZE);
|
||||
this.dirX = nbt.getDouble("DirX");
|
||||
|
||||
@@ -348,7 +348,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
|
||||
|
||||
// Fire tick event (server-side, non-preview only)
|
||||
if (!previewMode && !worldObj.isRemote) {
|
||||
EventHooks.onEnergyProjectileTick(this);
|
||||
EventHooks.onEnergyProjectileTick(this, ticksExisted);
|
||||
}
|
||||
|
||||
debugLogTick();
|
||||
@@ -738,8 +738,8 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
|
||||
motionY = nbt.getDouble("MotionY");
|
||||
motionZ = nbt.getDouble("MotionZ");
|
||||
syncPositionStateToCurrent(true);
|
||||
// Size & visual
|
||||
setProjectileSize(nbt.getFloat("Size"));
|
||||
// Size & visual (set target only - let the lerp in onUpdate smooth the transition)
|
||||
this.size = nbt.getFloat("Size");
|
||||
displayData.setRotationSpeed(nbt.getFloat("RotationSpeed"));
|
||||
displayData.setInnerAlpha(nbt.getFloat("InnerAlpha"));
|
||||
displayData.setOuterColorEnabled(nbt.getBoolean("OuterColorEnabled"));
|
||||
|
||||
@@ -124,7 +124,7 @@ import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Mod(modid = "customnpcs", name = "CustomNPC+", version = "1.11-prerelease3")
|
||||
@Mod(modid = "customnpcs", name = "CustomNPC+", version = "1.11")
|
||||
public class CustomNpcs {
|
||||
|
||||
@SidedProxy(clientSide = "noppes.npcs.client.ClientProxy", serverSide = "noppes.npcs.CommonProxy")
|
||||
|
||||
@@ -482,9 +482,9 @@ public class EventHooks {
|
||||
NpcAPI.EVENT_BUS.post(event);
|
||||
}
|
||||
|
||||
public static void onEnergyProjectileTick(EntityEnergyProjectile projectile) {
|
||||
public static void onEnergyProjectileTick(EntityEnergyProjectile projectile, int tick) {
|
||||
IEnergyProjectile wrapped = (IEnergyProjectile) NpcAPI.Instance().getIEntity(projectile);
|
||||
EnergyProjectileEvent.UpdateEvent event = new EnergyProjectileEvent.UpdateEvent(wrapped);
|
||||
EnergyProjectileEvent.UpdateEvent event = new EnergyProjectileEvent.UpdateEvent(wrapped, tick);
|
||||
ScriptController.Instance.globalNpcScripts.callScript(EnumScriptType.ENERGY_PROJECTILE_TICK, event);
|
||||
|
||||
Entity owner = projectile.worldObj.getEntityByID(projectile.getOwnerEntityId());
|
||||
|
||||
@@ -88,7 +88,7 @@ public class ScriptDocument {
|
||||
// Also matches ',' as a delimiter so that "int x, y, z;" is recognized
|
||||
// (the first declarator "int x," is captured; continuations are scanned manually).
|
||||
private static final Pattern FIELD_DECL_PATTERN = Pattern.compile(
|
||||
"\\b([A-Za-z_][a-zA-Z0-9_.<>,[ \\t]\\[\\]]*)[ \\t]+([a-zA-Z_][a-zA-Z0-9_]*)[ \\t]*(=|;|,)");
|
||||
"\\b([A-Za-z_][a-zA-Z0-9_.<>,[ \\t\\n\\r]\\[\\]]*)[ \\t]+([a-zA-Z_][a-zA-Z0-9_]*)[ \\t]*(=|;|,)");
|
||||
private static final Pattern NEW_TYPE_PATTERN = Pattern.compile("\\bnew\\s+([A-Za-z_][a-zA-Z0-9_]*)\\s*(?:<([^>]*)>)?");
|
||||
|
||||
// Cast expression pattern: captures type name inside cast parentheses
|
||||
@@ -785,8 +785,10 @@ public class ScriptDocument {
|
||||
*/
|
||||
private void parseScriptTypes() {
|
||||
// ========== PASS 1: Discover all type declarations (flat list) ==========
|
||||
// Regex only anchors on keyword + name — generic clauses are extracted manually
|
||||
// below to correctly handle nested angle brackets like <T extends Comparable<T>>.
|
||||
Pattern typeDecl = Pattern.compile(
|
||||
"(?:(?:public|private|protected|static|final|abstract)\\s+)*(class|interface|enum)\\s+([A-Za-z_][a-zA-Z0-9_]*)\\s*(?:<([^>]*)>)?\\s*(?:\\(\\))?\\s*(?:extends\\s+([A-Za-z_][a-zA-Z0-9_.]*))?\\s*(?:implements\\s+([A-Za-z_][a-zA-Z0-9_.,\\s]*))?\\s*\\{");
|
||||
"(?:(?:public|private|protected|static|final|abstract)\\s+)*(class|interface|enum)\\s+([A-Za-z_][a-zA-Z0-9_]*)");
|
||||
|
||||
List<RawTypeDeclaration> rawDeclarations = new ArrayList<>();
|
||||
Matcher m = typeDecl.matcher(text);
|
||||
@@ -796,16 +798,58 @@ public class ScriptDocument {
|
||||
|
||||
String kindStr = m.group(1);
|
||||
String typeName = m.group(2);
|
||||
String typeParamsClause = m.group(3);
|
||||
String extendsClause = m.group(4);
|
||||
String implementsClause = m.group(5);
|
||||
|
||||
int bodyStart = text.indexOf('{', m.start());
|
||||
int bodyEnd = findMatchingBrace(bodyStart);
|
||||
if (bodyEnd < 0) {
|
||||
bodyEnd = text.length();
|
||||
// Manually extract everything between the type name and the opening '{',
|
||||
// handling nested angle brackets so <T extends Comparable<T>> is captured whole.
|
||||
int scanPos = m.end();
|
||||
while (scanPos < text.length() && Character.isWhitespace(text.charAt(scanPos)))
|
||||
scanPos++;
|
||||
|
||||
// Extract type params clause <...> if present (depth-aware)
|
||||
String typeParamsClause = null;
|
||||
if (scanPos < text.length() && text.charAt(scanPos) == '<') {
|
||||
int depth = 1, i = scanPos + 1;
|
||||
while (i < text.length() && depth > 0) {
|
||||
char c = text.charAt(i++);
|
||||
if (c == '<') depth++;
|
||||
else if (c == '>') depth--;
|
||||
}
|
||||
if (depth == 0) {
|
||||
typeParamsClause = text.substring(scanPos + 1, i - 1);
|
||||
scanPos = i;
|
||||
while (scanPos < text.length() && Character.isWhitespace(text.charAt(scanPos)))
|
||||
scanPos++;
|
||||
}
|
||||
}
|
||||
|
||||
// Find the opening brace, extracting extends/implements from text between here and '{'
|
||||
int bracePos = scanPos;
|
||||
int angleDepth = 0;
|
||||
while (bracePos < text.length()) {
|
||||
char c = text.charAt(bracePos);
|
||||
if (c == '<') angleDepth++;
|
||||
else if (c == '>') angleDepth--;
|
||||
else if (c == '{' && angleDepth == 0) break;
|
||||
bracePos++;
|
||||
}
|
||||
if (bracePos >= text.length()) continue;
|
||||
|
||||
String betweenNameAndBrace = text.substring(scanPos, bracePos).trim();
|
||||
|
||||
// Parse extends/implements from that window
|
||||
String extendsClause = null;
|
||||
String implementsClause = null;
|
||||
java.util.regex.Matcher extM = Pattern.compile(
|
||||
"extends\\s+([A-Za-z_][a-zA-Z0-9_.]*(?:<[^{]*>)?)").matcher(betweenNameAndBrace);
|
||||
if (extM.find()) extendsClause = extM.group(1).trim();
|
||||
java.util.regex.Matcher implM = Pattern.compile(
|
||||
"implements\\s+(.+)$").matcher(betweenNameAndBrace);
|
||||
if (implM.find()) implementsClause = implM.group(1).trim();
|
||||
|
||||
int bodyStart = bracePos;
|
||||
int bodyEnd = findMatchingBrace(bodyStart);
|
||||
if (bodyEnd < 0) bodyEnd = text.length();
|
||||
|
||||
TypeInfo.Kind kind;
|
||||
switch (kindStr) {
|
||||
case "interface": kind = TypeInfo.Kind.INTERFACE; break;
|
||||
@@ -813,7 +857,7 @@ public class ScriptDocument {
|
||||
default: kind = TypeInfo.Kind.CLASS; break;
|
||||
}
|
||||
|
||||
String fullMatch = text.substring(m.start(), m.end());
|
||||
String fullMatch = text.substring(m.start(), bodyStart + 1);
|
||||
int modifiers = parseModifiers(fullMatch);
|
||||
JSDocInfo jsDoc = jsDocParser.extractJSDocBefore(text, m.start());
|
||||
|
||||
@@ -870,6 +914,11 @@ public class ScriptDocument {
|
||||
// Resolve extends clause (parent class inheritance)
|
||||
if (raw.extendsClause != null && !raw.extendsClause.trim().isEmpty()) {
|
||||
String parentName = raw.extendsClause.trim();
|
||||
// Strip generic args (e.g., "NumberBox<T>" -> "NumberBox") for type resolution
|
||||
int genIdx = parentName.indexOf('<');
|
||||
if (genIdx >= 0) {
|
||||
parentName = parentName.substring(0, genIdx);
|
||||
}
|
||||
TypeInfo parentType = resolveType(parentName, raw.declOffset);
|
||||
if (parentType == null) {
|
||||
parentType = TypeInfo.unresolved(parentName, parentName);
|
||||
@@ -884,6 +933,11 @@ public class ScriptDocument {
|
||||
String trimmedName = ifaceName.trim();
|
||||
if (trimmedName.isEmpty())
|
||||
continue;
|
||||
// Strip generic args (e.g., "Comparable<T>" -> "Comparable")
|
||||
int genIdx = trimmedName.indexOf('<');
|
||||
if (genIdx >= 0) {
|
||||
trimmedName = trimmedName.substring(0, genIdx);
|
||||
}
|
||||
TypeInfo ifaceType = resolveType(trimmedName, raw.declOffset);
|
||||
if (ifaceType == null) {
|
||||
ifaceType = TypeInfo.unresolved(trimmedName, trimmedName);
|
||||
@@ -920,14 +974,16 @@ public class ScriptDocument {
|
||||
String boundName = null;
|
||||
if (extendsIdx >= 0) {
|
||||
String afterExtends = trimmed.substring(extendsIdx + 7).trim();
|
||||
if (!afterExtends.isEmpty()) {
|
||||
// Split on & to get the primary bound (first one); additional bounds are interfaces
|
||||
String primaryBound = afterExtends.split("&")[0].trim();
|
||||
if (!primaryBound.isEmpty()) {
|
||||
int boundEndIdx = 0;
|
||||
while (boundEndIdx < afterExtends.length() &&
|
||||
(Character.isJavaIdentifierPart(afterExtends.charAt(boundEndIdx)) || afterExtends.charAt(boundEndIdx) == '.')) {
|
||||
while (boundEndIdx < primaryBound.length() &&
|
||||
(Character.isJavaIdentifierPart(primaryBound.charAt(boundEndIdx)) || primaryBound.charAt(boundEndIdx) == '.')) {
|
||||
boundEndIdx++;
|
||||
}
|
||||
if (boundEndIdx > 0) {
|
||||
boundName = afterExtends.substring(0, boundEndIdx);
|
||||
boundName = primaryBound.substring(0, boundEndIdx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -941,7 +997,7 @@ public class ScriptDocument {
|
||||
}
|
||||
scriptType.addDeclaredTypeParam(typeParam);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private TypeInfo parameterizeWithClause(TypeInfo baseType, String typeArgsClause, int position) {
|
||||
String[] argNames = typeArgsClause.split(",");
|
||||
@@ -964,42 +1020,68 @@ public class ScriptDocument {
|
||||
private void markTypeParamDeclarations(List<ScriptLine.Mark> marks, int afterNameEnd, ScriptTypeInfo typeInfo) {
|
||||
int ltPos = text.indexOf('<', afterNameEnd);
|
||||
if (ltPos < 0 || ltPos > afterNameEnd + 5) return;
|
||||
int gtPos = text.indexOf('>', ltPos);
|
||||
if (gtPos < 0) return;
|
||||
|
||||
String clause = text.substring(ltPos + 1, gtPos);
|
||||
int offset = ltPos + 1;
|
||||
for (TypeParamInfo param : typeInfo.getDeclaredTypeParams()) {
|
||||
int paramIdx = clause.indexOf(param.getName(), offset - (ltPos + 1));
|
||||
if (paramIdx >= 0) {
|
||||
int paramStart = ltPos + 1 + paramIdx;
|
||||
int paramLen = param.getName().length();
|
||||
int paramEnd = paramStart + paramLen;
|
||||
marks.add(new ScriptLine.Mark(paramStart, paramEnd, TokenType.GENERIC_TYPE_PARAM, param));
|
||||
int depth = 1, i = ltPos + 1;
|
||||
while (i < text.length() && depth > 0) {
|
||||
char c = text.charAt(i++);
|
||||
if (c == '<') depth++;
|
||||
else if (c == '>') depth--;
|
||||
}
|
||||
if (depth != 0) return;
|
||||
int gtPos = i - 1;
|
||||
|
||||
int searchFrom = paramIdx + param.getName().length();
|
||||
int extendsIdx = clause.indexOf("extends", searchFrom);
|
||||
if (extendsIdx >= 0) {
|
||||
int extendsStart = ltPos + 1 + extendsIdx;
|
||||
marks.add(new ScriptLine.Mark(extendsStart, extendsStart + 7, TokenType.KEYWORD));
|
||||
markTypeParamsInClause(marks, ltPos + 1, gtPos - 1, typeInfo.getDeclaredTypeParams());
|
||||
}
|
||||
|
||||
TypeInfo boundType = param.getBoundTypeInfo();
|
||||
if (boundType != null) {
|
||||
String remaining = clause.substring(extendsIdx + 7).trim();
|
||||
if (!remaining.isEmpty()) {
|
||||
int boundEnd = remaining.indexOf(',');
|
||||
if (boundEnd < 0) boundEnd = remaining.indexOf('&');
|
||||
String boundName = boundEnd < 0 ? remaining : remaining.substring(0, boundEnd).trim();
|
||||
if (!boundName.isEmpty()) {
|
||||
int boundStart = extendsStart + 7;
|
||||
while (boundStart < gtPos && Character.isWhitespace(text.charAt(boundStart))) boundStart++;
|
||||
marks.add(new ScriptLine.Mark(boundStart, boundStart + boundName.length(), TokenType.getByType(boundType), boundType));
|
||||
}
|
||||
}
|
||||
}
|
||||
private void markTypeParamsInClause(List<ScriptLine.Mark> marks, int clauseStart, int clauseEnd, List<TypeParamInfo> typeParams) {
|
||||
if (clauseStart >= clauseEnd) return;
|
||||
|
||||
String clause = text.substring(clauseStart, clauseEnd);
|
||||
Map<String, TypeParamInfo> paramMap = new HashMap<>();
|
||||
for (TypeParamInfo param : typeParams) {
|
||||
paramMap.put(param.getName(), param);
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
while (i < clause.length()) {
|
||||
char c = clause.charAt(i);
|
||||
|
||||
if (c == '<') {
|
||||
int innerStart = clauseStart + i + 1;
|
||||
int depth = 1;
|
||||
int j = i + 1;
|
||||
while (j < clause.length() && depth > 0) {
|
||||
char innerC = clause.charAt(j);
|
||||
if (innerC == '<') depth++;
|
||||
else if (innerC == '>') depth--;
|
||||
j++;
|
||||
}
|
||||
if (depth == 0) {
|
||||
markTypeParamsInClause(marks, innerStart, clauseStart + j - 1, typeParams);
|
||||
i = j;
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
} else if (c == '&') {
|
||||
i++;
|
||||
} else if (c == ',') {
|
||||
i++;
|
||||
} else if (Character.isJavaIdentifierStart(c)) {
|
||||
int start = i;
|
||||
while (i < clause.length() && Character.isJavaIdentifierPart(clause.charAt(i))) {
|
||||
i++;
|
||||
}
|
||||
String name = clause.substring(start, i);
|
||||
TypeParamInfo param = paramMap.get(name);
|
||||
if (param != null) {
|
||||
int absStart = clauseStart + start;
|
||||
int absEnd = clauseStart + i;
|
||||
marks.add(new ScriptLine.Mark(absStart, absEnd, TokenType.GENERIC_TYPE_PARAM, param));
|
||||
}
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Parse fields and methods inside a script-defined type.
|
||||
@@ -5038,8 +5120,8 @@ public class ScriptDocument {
|
||||
// Extended pattern to capture extends and implements clauses
|
||||
// Groups: 1=class|interface|enum, 2=TypeName, 3=extends clause, 4=implements clause
|
||||
Pattern classWithInheritance = Pattern.compile(
|
||||
"\\b(class|interface|enum)\\s+([A-Za-z_][a-zA-Z0-9_]*)\\s*(?:<[^>]*>)?\\s*(?:\\(\\))?\\s*" +
|
||||
"(?:(extends)\\s+([A-Za-z_][a-zA-Z0-9_.]*))?\\s*" +
|
||||
"\\b(class|interface|enum)\\s+([A-Za-z_][a-zA-Z0-9_]*)\\s*(?:<[^{]*>)?\\s*(?:\\(\\))?\\s*" +
|
||||
"(?:(extends)\\s+([A-Za-z_][a-zA-Z0-9_.]*)(?:<[^{]*>)?)?\\s*" +
|
||||
"(?:(implements)\\s+([A-Za-z_][a-zA-Z0-9_.,\\s]*))?\\s*(?=\\{)");
|
||||
|
||||
Matcher m = classWithInheritance.matcher(text);
|
||||
@@ -5087,7 +5169,7 @@ public class ScriptDocument {
|
||||
if (m.group(3) != null) {
|
||||
//Extends key word
|
||||
marks.add(new ScriptLine.Mark(m.start(3), m.end(3), TokenType.IMPORT_KEYWORD));
|
||||
markExtendsClause(marks, m.start(4), m.group(4));
|
||||
markExtendsClause(marks, m.start(4), m.group(4), typeInfo);
|
||||
}
|
||||
|
||||
// Mark implements clause
|
||||
@@ -5126,36 +5208,50 @@ public class ScriptDocument {
|
||||
* Mark the extends clause with proper coloring.
|
||||
* The parent class is colored based on its resolved type.
|
||||
*/
|
||||
private void markExtendsClause(List<ScriptLine.Mark> marks, int clauseStart, String parentName) {
|
||||
private void markExtendsClause(List<ScriptLine.Mark> marks, int clauseStart, String parentName, ScriptTypeInfo typeInfo) {
|
||||
String trimmedName = parentName.trim();
|
||||
if (trimmedName.isEmpty())
|
||||
return;
|
||||
|
||||
int actualStart = clauseStart;
|
||||
while (actualStart < clauseStart + trimmedName.length() &&
|
||||
Character.isWhitespace(text.charAt(actualStart))) {
|
||||
actualStart++;
|
||||
}
|
||||
|
||||
int baseNameEnd = actualStart;
|
||||
while (baseNameEnd < text.length() && Character.isJavaIdentifierPart(text.charAt(baseNameEnd))) {
|
||||
baseNameEnd++;
|
||||
}
|
||||
String baseName = text.substring(actualStart, baseNameEnd);
|
||||
|
||||
// Resolve the parent type with position context for inner class names
|
||||
TypeInfo parentType = resolveType(trimmedName, clauseStart);
|
||||
TokenType tokenType;
|
||||
|
||||
if (parentType != null && parentType.isResolved()) {
|
||||
// Use the type's specific token type (CLASS, INTERFACE, ENUM)
|
||||
tokenType = parentType.getTokenType();
|
||||
} else {
|
||||
// Unresolved - mark as undefined
|
||||
tokenType = TokenType.UNDEFINED_VAR;
|
||||
if (parentType == null) {
|
||||
parentType = TypeInfo.unresolved(trimmedName, trimmedName);
|
||||
}
|
||||
}
|
||||
|
||||
// Find actual position in text (might have leading whitespace in the group)
|
||||
int actualStart = clauseStart;
|
||||
String fullClause = parentName;
|
||||
while (actualStart < clauseStart + fullClause.length() &&
|
||||
Character.isWhitespace(text.charAt(actualStart))) {
|
||||
actualStart++;
|
||||
}
|
||||
marks.add(new ScriptLine.Mark(actualStart, baseNameEnd, tokenType, parentType));
|
||||
|
||||
marks.add(new ScriptLine.Mark(actualStart, actualStart + trimmedName.length(), tokenType, parentType));
|
||||
if (typeInfo != null && typeInfo.hasDeclaredTypeParams() && baseNameEnd < text.length() && text.charAt(baseNameEnd) == '<') {
|
||||
int ltPos = baseNameEnd;
|
||||
int depth = 1;
|
||||
int i = ltPos + 1;
|
||||
while (i < text.length() && depth > 0) {
|
||||
char c = text.charAt(i++);
|
||||
if (c == '<') depth++;
|
||||
else if (c == '>') depth--;
|
||||
}
|
||||
if (depth == 0) {
|
||||
markTypeParamsInClause(marks, ltPos + 1, i - 1, typeInfo.getDeclaredTypeParams());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -6059,7 +6155,7 @@ public class ScriptDocument {
|
||||
while (actualStart < i && Character.isWhitespace(text.charAt(actualStart))) {
|
||||
actualStart++;
|
||||
}
|
||||
int actualEnd = i;
|
||||
int actualEnd = i;
|
||||
while (actualEnd > actualStart && Character.isWhitespace(text.charAt(actualEnd - 1))) {
|
||||
actualEnd--;
|
||||
}
|
||||
|
||||
@@ -49,10 +49,16 @@ public class EnergyProjectileEvent extends CustomNPCsEvent implements IEnergyPro
|
||||
* energyProjectileTick
|
||||
*/
|
||||
public static class UpdateEvent extends EnergyProjectileEvent implements IEnergyProjectileEvent.UpdateEvent {
|
||||
public UpdateEvent(IEnergyProjectile projectile) {
|
||||
private final int tick;
|
||||
|
||||
public UpdateEvent(IEnergyProjectile projectile, int tick) {
|
||||
super(projectile);
|
||||
this.tick = tick;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTick() { return this.tick; }
|
||||
|
||||
@Override
|
||||
public String getHookName() {
|
||||
return EnumScriptType.ENERGY_PROJECTILE_TICK.function;
|
||||
|
||||
Reference in new issue
Block a user