added js code for quicklaunch
This commit is contained in:
@@ -38,6 +38,237 @@ data:
|
|||||||
port: 8080
|
port: 8080
|
||||||
assets-path: /app/config/assets
|
assets-path: /app/config/assets
|
||||||
|
|
||||||
|
document:
|
||||||
|
head: |
|
||||||
|
<style>
|
||||||
|
/* Glance Quick Launch overlay */
|
||||||
|
#gql-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0,0,0,.35);
|
||||||
|
display: none;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: center;
|
||||||
|
padding-top: 10vh;
|
||||||
|
z-index: 99999;
|
||||||
|
}
|
||||||
|
#gql-box {
|
||||||
|
width: min(720px, 92vw);
|
||||||
|
border-radius: 14px;
|
||||||
|
background: rgba(20, 22, 30, .92);
|
||||||
|
box-shadow: 0 10px 30px rgba(0,0,0,.35);
|
||||||
|
overflow: hidden;
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
}
|
||||||
|
#gql-input {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 14px 16px;
|
||||||
|
border: 0;
|
||||||
|
outline: none;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #fff;
|
||||||
|
background: rgba(0,0,0,.25);
|
||||||
|
}
|
||||||
|
#gql-hint {
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-size: 12px;
|
||||||
|
opacity: .75;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
#gql-list {
|
||||||
|
max-height: 360px;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
.gql-item {
|
||||||
|
padding: 10px 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #fff;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
.gql-item:hover, .gql-item.active { background: rgba(255,255,255,.10); }
|
||||||
|
.gql-title { font-weight: 600; }
|
||||||
|
.gql-url { font-size: 12px; opacity: .7; word-break: break-all; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(() => {
|
||||||
|
const MAX_RESULTS = 12;
|
||||||
|
|
||||||
|
const overlay = document.createElement('div');
|
||||||
|
overlay.id = 'gql-overlay';
|
||||||
|
overlay.innerHTML = `
|
||||||
|
<div id="gql-box" role="dialog" aria-modal="true">
|
||||||
|
<input id="gql-input" type="text" autocomplete="off" spellcheck="false" placeholder="Type to search bookmarks…"/>
|
||||||
|
<div id="gql-hint">↑/↓ to navigate • Enter to open • Esc to close</div>
|
||||||
|
<div id="gql-list"></div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
document.addEventListener('DOMContentLoaded', () => document.body.appendChild(overlay));
|
||||||
|
|
||||||
|
const $ = (sel) => overlay.querySelector(sel);
|
||||||
|
const input = () => $('#gql-input');
|
||||||
|
const list = () => $('#gql-list');
|
||||||
|
|
||||||
|
let indexed = [];
|
||||||
|
let activeIndex = 0;
|
||||||
|
let lastQuery = '';
|
||||||
|
|
||||||
|
function normalize(s) {
|
||||||
|
return (s || '').toLowerCase().replace(/\s+/g, ' ').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function indexLinks() {
|
||||||
|
// Prefer bookmarks widgets; fallback to any link inside widgets if Glance changes classes.
|
||||||
|
const anchors =
|
||||||
|
Array.from(document.querySelectorAll('.widget.widget-type-bookmarks a[href]')).length
|
||||||
|
? document.querySelectorAll('.widget.widget-type-bookmarks a[href]')
|
||||||
|
: document.querySelectorAll('.widget a[href]');
|
||||||
|
|
||||||
|
indexed = Array.from(anchors)
|
||||||
|
.map(a => ({
|
||||||
|
title: (a.textContent || '').trim(),
|
||||||
|
url: a.href
|
||||||
|
}))
|
||||||
|
.filter(x => x.title || x.url);
|
||||||
|
|
||||||
|
// Deduplicate by URL
|
||||||
|
const seen = new Set();
|
||||||
|
indexed = indexed.filter(x => (seen.has(x.url) ? false : seen.add(x.url)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function score(item, q) {
|
||||||
|
const t = normalize(item.title);
|
||||||
|
const u = normalize(item.url);
|
||||||
|
if (!q) return 0;
|
||||||
|
|
||||||
|
// simple scoring: prefix > includes, title > url
|
||||||
|
if (t.startsWith(q)) return 100;
|
||||||
|
if (t.includes(q)) return 70;
|
||||||
|
if (u.includes(q)) return 40;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function render(q) {
|
||||||
|
const results = indexed
|
||||||
|
.map(it => ({ ...it, s: score(it, q) }))
|
||||||
|
.filter(it => it.s >= 0)
|
||||||
|
.sort((a,b) => b.s - a.s)
|
||||||
|
.slice(0, MAX_RESULTS);
|
||||||
|
|
||||||
|
activeIndex = 0;
|
||||||
|
list().innerHTML = results.map((r, i) => `
|
||||||
|
<div class="gql-item ${i===0 ? 'active' : ''}" data-i="${i}">
|
||||||
|
<div class="gql-title">${escapeHtml(r.title || r.url)}</div>
|
||||||
|
<div class="gql-url">${escapeHtml(r.url)}</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
|
||||||
|
list().onclick = (e) => {
|
||||||
|
const item = e.target.closest('.gql-item');
|
||||||
|
if (!item) return;
|
||||||
|
const i = Number(item.dataset.i);
|
||||||
|
openResult(results, i, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setActive(i) {
|
||||||
|
const items = Array.from(list().querySelectorAll('.gql-item'));
|
||||||
|
items.forEach(el => el.classList.remove('active'));
|
||||||
|
if (items[i]) {
|
||||||
|
items[i].classList.add('active');
|
||||||
|
items[i].scrollIntoView({ block: 'nearest' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openOverlay(withInitialText = '') {
|
||||||
|
indexLinks();
|
||||||
|
overlay.style.display = 'flex';
|
||||||
|
input().value = withInitialText;
|
||||||
|
lastQuery = withInitialText;
|
||||||
|
const results = render(normalize(withInitialText));
|
||||||
|
input().focus();
|
||||||
|
|
||||||
|
function onInput() {
|
||||||
|
lastQuery = input().value;
|
||||||
|
render(normalize(lastQuery));
|
||||||
|
}
|
||||||
|
input().oninput = onInput;
|
||||||
|
|
||||||
|
input().onkeydown = (e) => {
|
||||||
|
const items = list().querySelectorAll('.gql-item');
|
||||||
|
const count = items.length;
|
||||||
|
|
||||||
|
if (e.key === 'Escape') { e.preventDefault(); closeOverlay(); return; }
|
||||||
|
if (e.key === 'ArrowDown' && count) { e.preventDefault(); activeIndex = Math.min(activeIndex+1, count-1); setActive(activeIndex); return; }
|
||||||
|
if (e.key === 'ArrowUp' && count) { e.preventDefault(); activeIndex = Math.max(activeIndex-1, 0); setActive(activeIndex); return; }
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
const q = normalize(input().value);
|
||||||
|
const resultsNow = indexed
|
||||||
|
.map(it => ({ ...it, s: score(it, q) }))
|
||||||
|
.filter(it => it.s >= 0)
|
||||||
|
.sort((a,b) => b.s - a.s)
|
||||||
|
.slice(0, MAX_RESULTS);
|
||||||
|
|
||||||
|
openResult(resultsNow, activeIndex, e.ctrlKey || e.metaKey);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
overlay.onclick = (e) => { if (e.target === overlay) closeOverlay(); };
|
||||||
|
}
|
||||||
|
|
||||||
|
function openResult(results, i, newTab) {
|
||||||
|
const r = results[i];
|
||||||
|
if (!r) return;
|
||||||
|
closeOverlay();
|
||||||
|
window.open(r.url, newTab ? '_blank' : '_self');
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeOverlay() {
|
||||||
|
overlay.style.display = 'none';
|
||||||
|
list().innerHTML = '';
|
||||||
|
activeIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeHtml(str) {
|
||||||
|
return (str || '').replace(/[&<>"']/g, c => ({
|
||||||
|
'&': '&', '<': '<', '>': '>', '"': '"', "'": '''
|
||||||
|
}[c]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global key handler: "just start typing"
|
||||||
|
window.addEventListener('keydown', (e) => {
|
||||||
|
if (overlay.style.display === 'flex') return;
|
||||||
|
|
||||||
|
// ignore when typing in inputs/textareas or using modifier shortcuts
|
||||||
|
const tag = (document.activeElement && document.activeElement.tagName || '').toLowerCase();
|
||||||
|
const typingIntoField = tag === 'input' || tag === 'textarea' || document.activeElement?.isContentEditable;
|
||||||
|
if (typingIntoField) return;
|
||||||
|
if (e.ctrlKey || e.metaKey || e.altKey) return;
|
||||||
|
|
||||||
|
// Printable character opens overlay with that character
|
||||||
|
if (e.key.length === 1 && !e.repeat) {
|
||||||
|
openOverlay(e.key);
|
||||||
|
e.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional: slash to open empty
|
||||||
|
if (e.key === '/') {
|
||||||
|
openOverlay('');
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
branding:
|
branding:
|
||||||
logo-url: https://web.dooplex.hu/static/DooPlex_logo_3.png
|
logo-url: https://web.dooplex.hu/static/DooPlex_logo_3.png
|
||||||
favicon-url: https://web.dooplex.hu/static/DooPlex_favicon_3.png
|
favicon-url: https://web.dooplex.hu/static/DooPlex_favicon_3.png
|
||||||
|
|||||||
Reference in New Issue
Block a user