work in progress on publishing docs
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
class CodeBlock extends HTMLElement {
|
||||
connectedCallback() {
|
||||
// Get the code content as text, preserving whitespace
|
||||
let code = this.textContent.replace(/\r\n?/g, "\n");
|
||||
// Remove leading/trailing blank lines
|
||||
code = code.replace(/^\s*\n/, '').replace(/\n\s*$/, '');
|
||||
// Find leading spaces from the first non-empty line
|
||||
const lines = code.split("\n");
|
||||
const firstLine = lines.find(line => line.trim().length > 0) || '';
|
||||
const leadingSpaces = firstLine.match(/^\s*/)[0].length;
|
||||
// Remove that many leading spaces from all lines
|
||||
const stripped = lines.map(line => line.slice(leadingSpaces)).join("\n");
|
||||
// Escape HTML special characters
|
||||
const escaped = stripped
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">");
|
||||
const lang = this.getAttribute('lang') || '';
|
||||
const langClass = lang ? `language-${lang}` : '';
|
||||
this.innerHTML = `<pre class=\"custom-code-block\"><code class=\"${langClass}\">${escaped}</code></pre>`;
|
||||
// If Prism or highlight.js is present, trigger highlighting
|
||||
if (window.Prism && Prism.highlightElement) {
|
||||
Prism.highlightElement(this.querySelector('code'));
|
||||
} else if (window.hljs && hljs.highlightElement) {
|
||||
hljs.highlightElement(this.querySelector('code'));
|
||||
}
|
||||
}
|
||||
}
|
||||
customElements.define('code-block', CodeBlock);
|
||||
@@ -0,0 +1,32 @@
|
||||
class CodeInline extends HTMLElement {
|
||||
connectedCallback() {
|
||||
// Get the code content as text
|
||||
let code = this.textContent;
|
||||
|
||||
// Escape HTML special characters
|
||||
code = code
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">");
|
||||
|
||||
// Highlight variables: $variable, {variable}, or %variable%
|
||||
code = code.replace(/(\$[a-zA-Z_][\w]*)|(\{[^\}]+\})|(%[^%]+%)/g, match =>
|
||||
`<span class="inline-var">${match}</span>`
|
||||
);
|
||||
|
||||
// Get language attribute for potential syntax highlighting
|
||||
const lang = this.getAttribute('lang') || '';
|
||||
const langClass = lang ? `language-${lang}` : '';
|
||||
|
||||
this.innerHTML = `<code class="custom-inline-code ${langClass}">${code}</code>`;
|
||||
|
||||
// If Prism or highlight.js is present, trigger highlighting
|
||||
if (window.Prism && Prism.highlightElement) {
|
||||
Prism.highlightElement(this.querySelector('code'));
|
||||
} else if (window.hljs && hljs.highlightElement) {
|
||||
hljs.highlightElement(this.querySelector('code'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('code-inline', CodeInline);
|
||||
@@ -0,0 +1,54 @@
|
||||
|
||||
class Header extends HTMLElement {
|
||||
|
||||
constructor(){
|
||||
super();
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.innerHTML = /*html*/`
|
||||
<div class="header-box">
|
||||
<a class="logo" href="/index.html">Spearhead</a>
|
||||
<span class="subTitle">mission making, made easy</span>
|
||||
<div class="header-right">
|
||||
<nav>
|
||||
<a href="/index.html">Home</a>
|
||||
|
||||
<div class="dropdown">
|
||||
<a href="/pages/tutorials.html">Tutorials</a>
|
||||
<div class="dropdown-content">
|
||||
<a href="/pages/include-script.html">Include the Script</a>
|
||||
<a href="/pages/first-start.html">First Start</a>
|
||||
<a href="/pages/advanced/CAP.html">Advanced: CAP</a>
|
||||
<a href="/pages/advanced/missions.html">Advanced: Missions</a>
|
||||
<a href="/pages/advanced/map-markings.html">Map Markings</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<a href="/pages/persistence.html">Persistence</a>
|
||||
<a href="/pages/reference.html">Reference</a>
|
||||
<a href="/pages/spearheadapi.html">API</a>
|
||||
<a href="/pages/about_us.html">About Us</a>
|
||||
</nav>
|
||||
<button id="theme-toggle" class="theme-toggle" title="Toggle light/dark theme">
|
||||
<svg id="moon-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M12 11.807A9.002 9.002 0 0 1 10.049 2a9.942 9.942 0 0 0-5.12 2.735c-3.905 3.905-3.905 10.237 0 14.142 3.906 3.906 10.237 3.905 14.143 0a9.946 9.946 0 0 0 2.735-5.119A9.003 9.003 0 0 1 12 11.807z"/>
|
||||
</svg>
|
||||
<svg id="sun-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" style="display: none;">
|
||||
<path d="M6.995 12c0 2.761 2.246 5.007 5.007 5.007s5.007-2.246 5.007-5.007-2.246-5.007-5.007-5.007S6.995 9.239 6.995 12zM12 8.993c1.658 0 3.007 1.349 3.007 3.007S13.658 15.007 12 15.007 8.993 13.658 8.993 12 10.342 8.993 12 8.993zM10.998 19H12.998V22H10.998zM10.998 2H12.998V5H10.998zM1.998 11H4.998V13H1.998zM18.998 11H21.998V13H18.998z"/>
|
||||
<path transform="rotate(-45.017 5.986 18.01)" d="M4.986 17.01H6.986V19.01H4.986z"/>
|
||||
<path transform="rotate(-45.001 18.008 5.99)" d="M17.008 4.99H19.008V6.99H17.008z"/>
|
||||
<path transform="rotate(-134.983 5.988 5.99)" d="M4.988 4.99H6.988V6.99H4.988z"/>
|
||||
<path transform="rotate(134.999 18.008 18.01)" d="M17.008 17.01H19.008V19.01H17.008z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
customElements.define('app-header', Header);
|
||||
@@ -0,0 +1,119 @@
|
||||
class DownloadScript extends HTMLElement {
|
||||
|
||||
constructor(){
|
||||
super();
|
||||
this.cacheKey = 'spearhead-latest-release';
|
||||
this.cacheExpiryMinutes = 15;
|
||||
}
|
||||
|
||||
getCachedData() {
|
||||
const cached = localStorage.getItem(this.cacheKey);
|
||||
if (!cached) return null;
|
||||
|
||||
const { data, timestamp } = JSON.parse(cached);
|
||||
const now = Date.now();
|
||||
const expiry = this.cacheExpiryMinutes * 60 * 1000; // 15 minutes in ms
|
||||
|
||||
if (now - timestamp > expiry) {
|
||||
localStorage.removeItem(this.cacheKey);
|
||||
return null;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
setCachedData(data) {
|
||||
const cacheItem = {
|
||||
data: data,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
localStorage.setItem(this.cacheKey, JSON.stringify(cacheItem));
|
||||
}
|
||||
|
||||
async fetchLatestRelease() {
|
||||
// Check cache first
|
||||
const cachedData = this.getCachedData();
|
||||
if (cachedData) {
|
||||
return cachedData;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('https://api.github.com/repos/dutchie031/Spearhead/releases/latest');
|
||||
const data = await response.json();
|
||||
|
||||
// Cache the data
|
||||
this.setCachedData(data);
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching latest release:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async fetchReleaseAssets(url){
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching release assets:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async connectedCallback() {
|
||||
|
||||
var version = "?";
|
||||
var timestamp = "?";
|
||||
var size = "?";
|
||||
var href = "https://github.com/dutchie031/Spearhead/releases"
|
||||
|
||||
const latestRelease = await this.fetchLatestRelease();
|
||||
if (latestRelease) {
|
||||
version = latestRelease.tag_name;
|
||||
timestamp = new Date(latestRelease.published_at).toLocaleDateString('en-CA');
|
||||
|
||||
const assets = await this.fetchReleaseAssets(latestRelease.assets_url);
|
||||
if (assets && assets.length > 0) {
|
||||
const spearheadAsset = assets.find(asset => asset.name.includes('spearhead'));
|
||||
if (spearheadAsset) {
|
||||
size = (spearheadAsset.size / 1024).toFixed(2) + 'kb';
|
||||
href = spearheadAsset.browser_download_url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.innerHTML = /*html*/`
|
||||
<div class="latest-version-download-box">
|
||||
<style>
|
||||
.latest-version-download-box {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.latest-version-download-content {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 8px;
|
||||
padding: 0px 16px;
|
||||
}
|
||||
|
||||
.version-text {
|
||||
font-weight: bold;
|
||||
color: #f5efefff
|
||||
}
|
||||
</style>
|
||||
<div class="latest-version-download-content">
|
||||
<p>
|
||||
<span class="version-text">${version}</span> (${timestamp}) <br>
|
||||
<a style="text-decoration: underline;" target="_blank" href="${href}">Download</a> (${size}) <br>
|
||||
<a style="text-decoration: underline;" target="_blank" href="https://github.com/dutchie031/Spearhead/releases">See All Versions here</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
customElements.define('download-spearhead', DownloadScript);
|
||||
@@ -0,0 +1,35 @@
|
||||
class Note extends HTMLElement {
|
||||
connectedCallback() {
|
||||
// Get the note content
|
||||
const content = this.innerHTML;
|
||||
|
||||
// Get optional type attribute for different note styles
|
||||
const type = this.getAttribute('type') || 'default';
|
||||
|
||||
// Get optional title attribute
|
||||
const title = this.getAttribute('title');
|
||||
|
||||
// Build the note HTML
|
||||
let noteHTML = '<div class="note';
|
||||
|
||||
// Add type-specific class if provided
|
||||
if (type !== 'default') {
|
||||
noteHTML += ` note-${type}`;
|
||||
}
|
||||
|
||||
noteHTML += '">';
|
||||
|
||||
// Add title if provided
|
||||
if (title) {
|
||||
noteHTML += `<h4 class="note-title">${title}</h4>`;
|
||||
}
|
||||
|
||||
// Add the content
|
||||
noteHTML += content;
|
||||
noteHTML += '</div>';
|
||||
|
||||
this.innerHTML = noteHTML;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('note-box', Note);
|
||||
@@ -0,0 +1,198 @@
|
||||
class Sidebar extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.navItems = [];
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.render();
|
||||
this.scanHeaders();
|
||||
this.setupScrollListener();
|
||||
this.highlightActiveSection();
|
||||
}
|
||||
|
||||
scanHeaders() {
|
||||
// Clear existing nav items
|
||||
this.navItems = [];
|
||||
|
||||
// Find all h2, h3, and h4 elements with IDs in the content area
|
||||
const contentWrapper = document.querySelector('.content-wrapper');
|
||||
if (!contentWrapper) return;
|
||||
|
||||
const headers = contentWrapper.querySelectorAll('h2[id], h3[id], h4[id]');
|
||||
|
||||
headers.forEach(header => {
|
||||
const id = header.getAttribute('id');
|
||||
const text = header.textContent.trim();
|
||||
const level = header.tagName.toLowerCase();
|
||||
|
||||
this.navItems.push({
|
||||
id,
|
||||
text,
|
||||
level,
|
||||
element: header
|
||||
});
|
||||
});
|
||||
|
||||
this.renderNavigation();
|
||||
}
|
||||
|
||||
renderNavigation() {
|
||||
const ul = this.querySelector('ul');
|
||||
if (!ul) return;
|
||||
|
||||
// Clear existing content
|
||||
ul.innerHTML = '';
|
||||
|
||||
let currentH2Li = null;
|
||||
let currentH3Li = null;
|
||||
|
||||
this.navItems.forEach(item => {
|
||||
if (item.level === 'h2') {
|
||||
// Create h2 item
|
||||
const li = document.createElement('li');
|
||||
const a = document.createElement('a');
|
||||
a.href = `#${item.id}`;
|
||||
a.className = 'side-nav-h2';
|
||||
a.textContent = item.text;
|
||||
a.addEventListener('click', (e) => this.handleNavClick(e, item.id));
|
||||
|
||||
li.appendChild(a);
|
||||
ul.appendChild(li);
|
||||
currentH2Li = li;
|
||||
currentH3Li = null;
|
||||
} else if (item.level === 'h3' && currentH2Li) {
|
||||
// Create h3 item under current h2
|
||||
let subUl = currentH2Li.querySelector('ul');
|
||||
if (!subUl) {
|
||||
subUl = document.createElement('ul');
|
||||
currentH2Li.appendChild(subUl);
|
||||
}
|
||||
|
||||
const li = document.createElement('li');
|
||||
const a = document.createElement('a');
|
||||
a.href = `#${item.id}`;
|
||||
a.className = 'side-nav-h3';
|
||||
a.textContent = item.text;
|
||||
a.addEventListener('click', (e) => this.handleNavClick(e, item.id));
|
||||
|
||||
li.appendChild(a);
|
||||
subUl.appendChild(li);
|
||||
currentH3Li = li;
|
||||
} else if (item.level === 'h4' && currentH3Li) {
|
||||
// Create h4 item under current h3
|
||||
let subUl = currentH3Li.querySelector('ul');
|
||||
if (!subUl) {
|
||||
subUl = document.createElement('ul');
|
||||
currentH3Li.appendChild(subUl);
|
||||
}
|
||||
|
||||
const li = document.createElement('li');
|
||||
const a = document.createElement('a');
|
||||
a.href = `#${item.id}`;
|
||||
a.className = 'side-nav-h4';
|
||||
a.textContent = item.text;
|
||||
a.addEventListener('click', (e) => this.handleNavClick(e, item.id));
|
||||
|
||||
li.appendChild(a);
|
||||
subUl.appendChild(li);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleNavClick(e, targetId) {
|
||||
e.preventDefault();
|
||||
|
||||
// Remove active class from all links
|
||||
this.querySelectorAll('a').forEach(link => {
|
||||
link.classList.remove('active');
|
||||
});
|
||||
|
||||
// Add active class to clicked link
|
||||
e.target.classList.add('active');
|
||||
|
||||
// Smooth scroll to target
|
||||
const targetElement = document.getElementById(targetId);
|
||||
if (targetElement) {
|
||||
targetElement.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setupScrollListener() {
|
||||
let ticking = false;
|
||||
|
||||
const handleScroll = () => {
|
||||
if (!ticking) {
|
||||
requestAnimationFrame(() => {
|
||||
this.highlightActiveSection();
|
||||
ticking = false;
|
||||
});
|
||||
ticking = true;
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
}
|
||||
|
||||
highlightActiveSection() {
|
||||
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
||||
const offset = 100; // Offset for highlighting
|
||||
|
||||
let activeId = '';
|
||||
|
||||
// Find the currently visible section
|
||||
this.navItems.forEach(item => {
|
||||
const element = item.element;
|
||||
const rect = element.getBoundingClientRect();
|
||||
const elementTop = rect.top + scrollTop;
|
||||
|
||||
if (elementTop <= scrollTop + offset) {
|
||||
activeId = item.id;
|
||||
}
|
||||
});
|
||||
|
||||
// Update active state
|
||||
this.querySelectorAll('a').forEach(link => {
|
||||
link.classList.remove('active');
|
||||
});
|
||||
|
||||
if (activeId !== nil && activeId !== '') {
|
||||
const activeLink = this.querySelector(`a[href="#${activeId}"]`);
|
||||
if (activeLink) {
|
||||
activeLink.classList.add('active');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
this.innerHTML = `
|
||||
<div class="side-nav">
|
||||
<h4 class="side-nav-title"></h4>
|
||||
<ul>
|
||||
<!-- Navigation items will be populated automatically -->
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Method to refresh the sidebar when content changes
|
||||
refresh() {
|
||||
this.scanHeaders();
|
||||
}
|
||||
|
||||
// Method to set the sidebar title
|
||||
setTitle(title) {
|
||||
const titleElement = this.querySelector('.side-nav-title');
|
||||
if (titleElement) {
|
||||
titleElement.textContent = title;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register the custom element
|
||||
customElements.define('app-sidebar', Sidebar);
|
||||
|
||||
export default Sidebar;
|
||||
Reference in New Issue
Block a user