0.2.1
This commit is contained in:
@@ -16,7 +16,7 @@ func HomePage(w http.ResponseWriter, r *http.Request) {
|
||||
if n.Beta {
|
||||
badge = ` <span class="beta-pill">Beta</span>`
|
||||
}
|
||||
items.WriteString(`<button type="button" class="home-tool" data-path="` + n.Path + `"><span class="home-tool-title">` + n.Label + badge + `</span><span class="home-tool-desc">` + n.Description + `</span></button>`)
|
||||
items.WriteString(`<button type="button" class="home-tool" draggable="true" data-path="` + n.Path + `"><span class="home-tool-title">` + n.Label + badge + `</span><span class="home-tool-desc">` + n.Description + `</span></button>`)
|
||||
}
|
||||
|
||||
body := `
|
||||
@@ -43,6 +43,7 @@ func HomePage(w http.ResponseWriter, r *http.Request) {
|
||||
font:inherit;
|
||||
}
|
||||
.home-tool:hover { background:#33373a; border-color:#007fd4; }
|
||||
.home-tool.dragging { opacity:0.55; }
|
||||
.home-tool-title { display:flex; align-items:center; justify-content:space-between; gap:8px; font-size:12px; font-weight:600; }
|
||||
.home-tool-desc { display:block; margin-top:4px; color:#a7a7a7; font-size:11px; line-height:1.35; }
|
||||
</style>
|
||||
|
||||
@@ -104,6 +104,7 @@ const SharedCSS = `
|
||||
}
|
||||
.sidebar .nav-link:hover { background: #2a2d2e; }
|
||||
.sidebar .nav-link.active { background: #094771; color: #fff; }
|
||||
.sidebar .nav-link.dragging { opacity: 0.55; }
|
||||
.nav-icon {
|
||||
width: 28px;
|
||||
height: 26px;
|
||||
@@ -478,7 +479,7 @@ func navInitials(label string) string {
|
||||
case "Mintegral Checker":
|
||||
return "MiC"
|
||||
case "Device Simulator":
|
||||
return "SIM"
|
||||
return "DS"
|
||||
}
|
||||
words := strings.Fields(label)
|
||||
if len(words) == 0 {
|
||||
@@ -512,7 +513,7 @@ func Page(activePath, title, body string) string {
|
||||
if n.Beta {
|
||||
label += `<span class="beta-pill">Beta</span>`
|
||||
}
|
||||
tabs.WriteString(`<button type="button" class="nav-link` + cls + `" data-path="` + n.Path + `" title="` + n.Label + `"><span class="nav-icon">` + navInitials(n.Label) + `</span><span class="nav-text">` + label + `</span></button>`)
|
||||
tabs.WriteString(`<button type="button" class="nav-link` + cls + `" data-path="` + n.Path + `" draggable="` + boolAttr(n.Path != "/") + `" title="` + n.Label + `"><span class="nav-icon">` + navInitials(n.Label) + `</span><span class="nav-text">` + label + `</span></button>`)
|
||||
}
|
||||
|
||||
betaButtonLabel := "Show beta"
|
||||
@@ -552,10 +553,72 @@ document.getElementById('sidebarToggle').addEventListener('click', () => {
|
||||
});
|
||||
document.querySelectorAll('[data-path]').forEach((element) => {
|
||||
element.addEventListener('click', () => {
|
||||
if (element.dataset.dragging === 'true') {
|
||||
element.dataset.dragging = 'false';
|
||||
return;
|
||||
}
|
||||
const path = element.dataset.path;
|
||||
if (path) window.location.href = path;
|
||||
});
|
||||
});
|
||||
const TOOL_ORDER_KEY = 'hplToolbox.toolOrder.v1';
|
||||
function getToolOrder() {
|
||||
try {
|
||||
const parsed = JSON.parse(localStorage.getItem(TOOL_ORDER_KEY) || '[]');
|
||||
return Array.isArray(parsed) ? parsed.filter(path => typeof path === 'string') : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
function setToolOrder(paths) {
|
||||
localStorage.setItem(TOOL_ORDER_KEY, JSON.stringify(paths));
|
||||
}
|
||||
function applyStoredToolOrder(container, selector) {
|
||||
if (!container) return;
|
||||
const order = getToolOrder();
|
||||
if (!order.length) return;
|
||||
const elements = Array.from(container.querySelectorAll(selector));
|
||||
const byPath = new Map(elements.map(el => [el.dataset.path, el]));
|
||||
const home = byPath.get('/');
|
||||
const ordered = order.map(path => byPath.get(path)).filter(Boolean);
|
||||
const orderedSet = new Set(ordered);
|
||||
const remaining = elements.filter(el => el !== home && !orderedSet.has(el));
|
||||
if (home) container.appendChild(home);
|
||||
ordered.concat(remaining).forEach(el => container.appendChild(el));
|
||||
}
|
||||
function setupToolReorder(container, selector) {
|
||||
if (!container) return;
|
||||
applyStoredToolOrder(container, selector);
|
||||
let dragged = null;
|
||||
container.querySelectorAll(selector).forEach(element => {
|
||||
if (element.dataset.path === '/') return;
|
||||
element.addEventListener('dragstart', event => {
|
||||
dragged = element;
|
||||
element.classList.add('dragging');
|
||||
event.dataTransfer.effectAllowed = 'move';
|
||||
event.dataTransfer.setData('text/plain', element.dataset.path || '');
|
||||
});
|
||||
element.addEventListener('dragend', () => {
|
||||
element.classList.remove('dragging');
|
||||
element.dataset.dragging = 'true';
|
||||
dragged = null;
|
||||
const paths = Array.from(container.querySelectorAll(selector))
|
||||
.map(el => el.dataset.path)
|
||||
.filter(path => path && path !== '/');
|
||||
setToolOrder(paths);
|
||||
setTimeout(() => { element.dataset.dragging = 'false'; }, 0);
|
||||
});
|
||||
element.addEventListener('dragover', event => {
|
||||
event.preventDefault();
|
||||
if (!dragged || dragged === element || element.dataset.path === '/') return;
|
||||
const rect = element.getBoundingClientRect();
|
||||
const after = event.clientY > rect.top + rect.height / 2;
|
||||
container.insertBefore(dragged, after ? element.nextSibling : element);
|
||||
});
|
||||
});
|
||||
}
|
||||
setupToolReorder(document.querySelector('.sidebar-nav'), '.nav-link');
|
||||
setupToolReorder(document.querySelector('.home-tools'), '.home-tool');
|
||||
document.getElementById('betaToolsToggle').addEventListener('click', async (event) => {
|
||||
const enabled = event.currentTarget.dataset.enabled === 'true';
|
||||
await fetch('/api/betaTools', {
|
||||
|
||||
@@ -457,7 +457,7 @@ func shareHandler(s *activeShare) http.Handler {
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
_, _ = w.Write(s.files[0].buf)
|
||||
_, _ = w.Write([]byte(mobilePreviewPage(s.files[0])))
|
||||
case "/file":
|
||||
fallthrough
|
||||
case "/file/0":
|
||||
@@ -481,7 +481,7 @@ func shareHandler(s *activeShare) http.Handler {
|
||||
if parts[0] == "view" {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
_, _ = w.Write(file.buf)
|
||||
_, _ = w.Write([]byte(mobilePreviewPage(file)))
|
||||
return
|
||||
}
|
||||
safeName := safeFilename(file.filename)
|
||||
@@ -499,6 +499,35 @@ func shareHandler(s *activeShare) http.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
func mobilePreviewPage(file sharedFile) string {
|
||||
playableJSON, _ := json.Marshal(string(file.buf))
|
||||
playableScriptValue := strings.ReplaceAll(string(playableJSON), "</script", "<\\/script")
|
||||
playableScriptValue = strings.ReplaceAll(playableScriptValue, "</SCRIPT", "<\\/SCRIPT")
|
||||
title := html.EscapeString(file.filename)
|
||||
return `<!DOCTYPE html>
|
||||
<html><head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||
<title>` + title + `</title>
|
||||
<style>
|
||||
html, body { width: 100%; height: 100%; margin: 0; overflow: hidden; background: #000; }
|
||||
iframe { position: fixed; inset: 0; width: 100%; height: 100%; border: 0; background: #000; }
|
||||
.boot { position: fixed; inset: 0; display: grid; place-items: center; color: #777; font: 12px system-ui, sans-serif; }
|
||||
</style>
|
||||
</head><body>
|
||||
<div id="boot" class="boot">Loading preview...</div>
|
||||
<iframe id="playable" sandbox="allow-scripts allow-same-origin allow-pointer-lock allow-forms allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation"></iframe>
|
||||
<script>
|
||||
const playableHtml = ` + playableScriptValue + `;
|
||||
setTimeout(() => {
|
||||
const frame = document.getElementById('playable');
|
||||
document.getElementById('boot').style.display = 'none';
|
||||
frame.srcdoc = playableHtml;
|
||||
}, 750);
|
||||
</script>
|
||||
</body></html>`
|
||||
}
|
||||
|
||||
var unsafeFilenameRx = regexp.MustCompile(`[^A-Za-z0-9._-]`)
|
||||
|
||||
func safeFilename(s string) string { return unsafeFilenameRx.ReplaceAllString(s, "_") }
|
||||
|
||||
Reference in New Issue
Block a user