Refactor js

This commit is contained in:
2026-01-15 20:05:41 +01:00
parent 34ff7f1a7f
commit 8a97197f3e

230
script.js
View File

@@ -262,12 +262,9 @@
})(); })();
// ============================================================ // ============================================================
// Gallery Thumbnails // Gallery Thumbnails Module
// ============================================================ // ============================================================
function initGalleryThumbs() { var GalleryThumbnails = (function () {
var imgs = document.querySelectorAll('div.luxtools-gallery img[data-thumb-src]');
if (!imgs || !imgs.length) return;
function loadThumb(img) { function loadThumb(img) {
var src = img.getAttribute('data-thumb-src') || ''; var src = img.getAttribute('data-thumb-src') || '';
if (!src) return; if (!src) return;
@@ -286,107 +283,124 @@
pre.src = src; pre.src = src;
} }
if ('IntersectionObserver' in window) { function init() {
var io = new window.IntersectionObserver(function (entries) { var imgs = document.querySelectorAll('div.luxtools-gallery img[data-thumb-src]');
entries.forEach(function (entry) { if (!imgs || !imgs.length) return;
if (!entry.isIntersecting) return;
loadThumb(entry.target);
io.unobserve(entry.target);
});
}, { rootMargin: '200px' });
imgs.forEach(function (img) { io.observe(img); }); if ('IntersectionObserver' in window) {
} else { var io = new window.IntersectionObserver(function (entries) {
// Fallback: load soon after initial render entries.forEach(function (entry) {
window.setTimeout(function () { if (!entry.isIntersecting) return;
imgs.forEach(loadThumb); loadThumb(entry.target);
}, 0); io.unobserve(entry.target);
} });
} }, { rootMargin: '200px' });
// ============================================================ imgs.forEach(function (img) { io.observe(img); });
// Open Service (file:// links) } else {
// ============================================================ // Fallback: load soon after initial render
function getServiceUrl(el) { window.setTimeout(function () {
var url = el.getAttribute('data-service-url') || ''; imgs.forEach(loadThumb);
url = (url || '').trim(); }, 0);
if (!url) return '';
// strip trailing slashes
return url.replace(/\/+$/, '');
}
function pingOpenViaImage(el, rawPath) {
var baseUrl = getServiceUrl(el);
if (!baseUrl) return;
var url = baseUrl + '/open?path=' + encodeURIComponent(rawPath);
// Fire-and-forget without CORS.
try {
var img = new window.Image();
img.src = url;
} catch (e) {
// ignore
}
}
function openViaService(el, rawPath) {
var baseUrl = getServiceUrl(el);
if (!baseUrl) return Promise.reject(new Error('No client service configured'));
var headers = {
'Content-Type': 'application/json'
};
return window.fetch(baseUrl + '/open', {
method: 'POST',
mode: 'cors',
credentials: 'omit',
headers: headers,
body: JSON.stringify({ path: rawPath })
}).then(function (res) {
if (!res.ok) {
return res.json().catch(function () { return null; }).then(function (body) {
var msg = (body && body.message) ? body.message : ('HTTP ' + res.status);
throw new Error(msg);
});
} }
return res.json().catch(function () { return { ok: true }; });
});
}
function normalizeToFileUrl(path) {
if (!path) return '';
// already a file URL
if (/^file:\/\//i.test(path)) return path;
// UNC path: \\server\share\path
if (/^\\\\/.test(path)) {
var p = path.replace(/^\\\\/, '');
p = p.replace(/\\/g, '/');
return 'file://///' + p;
} }
// Windows drive: C:\path\to\file return {
if (/^[a-zA-Z]:\\/.test(path)) { init: init
var drive = path[0].toUpperCase(); };
var rest = path.slice(2).replace(/\\/g, '/'); })();
return 'file:///' + drive + ':' + rest;
// ============================================================
// Open Service Module (file:// links)
// ============================================================
var OpenService = (function () {
function getServiceUrl(el) {
var url = el.getAttribute('data-service-url') || '';
url = (url || '').trim();
if (!url) return '';
// strip trailing slashes
return url.replace(/\/+$/, '');
} }
// POSIX absolute: /home/user/file function pingOpenViaImage(el, rawPath) {
if (path[0] === '/') { var baseUrl = getServiceUrl(el);
return 'file://' + path; if (!baseUrl) return;
var url = baseUrl + '/open?path=' + encodeURIComponent(rawPath);
// Fire-and-forget without CORS.
try {
var img = new window.Image();
img.src = url;
} catch (e) {
// ignore
}
} }
// Fall back to using the provided string. function openViaService(el, rawPath) {
return path; var baseUrl = getServiceUrl(el);
} if (!baseUrl) return Promise.reject(new Error('No client service configured'));
function initScratchpads() { var headers = {
var pads = document.querySelectorAll('div.luxtools-scratchpad[data-luxtools-scratchpad="1"]'); 'Content-Type': 'application/json'
if (!pads || !pads.length) return; };
return window.fetch(baseUrl + '/open', {
method: 'POST',
mode: 'cors',
credentials: 'omit',
headers: headers,
body: JSON.stringify({ path: rawPath })
}).then(function (res) {
if (!res.ok) {
return res.json().catch(function () { return null; }).then(function (body) {
var msg = (body && body.message) ? body.message : ('HTTP ' + res.status);
throw new Error(msg);
});
}
return res.json().catch(function () { return { ok: true }; });
});
}
function normalizeToFileUrl(path) {
if (!path) return '';
// already a file URL
if (/^file:\/\//i.test(path)) return path;
// UNC path: \\server\share\path
if (/^\\\\/.test(path)) {
var p = path.replace(/^\\\\/, '');
p = p.replace(/\\/g, '/');
return 'file://///' + p;
}
// Windows drive: C:\path\to\file
if (/^[a-zA-Z]:\\/.test(path)) {
var drive = path[0].toUpperCase();
var rest = path.slice(2).replace(/\\/g, '/');
return 'file:///' + drive + ':' + rest;
}
// POSIX absolute: /home/user/file
if (path[0] === '/') {
return 'file://' + path;
}
// Fall back to using the provided string.
return path;
}
return {
openViaService: openViaService,
pingOpenViaImage: pingOpenViaImage,
normalizeToFileUrl: normalizeToFileUrl
};
})();
// ============================================================
// Scratchpads Module
// ============================================================
var Scratchpads = (function () {
function setEditMode(root, isEditing) { function setEditMode(root, isEditing) {
if (!root || !root.classList) return; if (!root || !root.classList) return;
@@ -511,7 +525,7 @@
setStatus(root, ''); setStatus(root, '');
} }
document.addEventListener('click', function (e) { function onClick(e) {
var t = e.target; var t = e.target;
if (!t) return; if (!t) return;
@@ -550,8 +564,18 @@
e.preventDefault(); e.preventDefault();
closeEditor(rootC); closeEditor(rootC);
} }
}, true); }
}
function init() {
var pads = document.querySelectorAll('div.luxtools-scratchpad[data-luxtools-scratchpad="1"]');
if (!pads || !pads.length) return;
document.addEventListener('click', onClick, true);
}
return {
init: init
};
})();
// ============================================================ // ============================================================
// Click Handlers // Click Handlers
@@ -598,14 +622,14 @@
if (!raw) return; if (!raw) return;
// Prefer local client service. // Prefer local client service.
openViaService(el, raw) OpenService.openViaService(el, raw)
.catch(function (err) { .catch(function (err) {
// If the browser blocks the request before it reaches localhost (mixed-content, // If the browser blocks the request before it reaches localhost (mixed-content,
// extensions, stricter CORS handling), fall back to a no-CORS GET ping. // extensions, stricter CORS handling), fall back to a no-CORS GET ping.
pingOpenViaImage(el, raw); OpenService.pingOpenViaImage(el, raw);
// Fallback to old behavior (often blocked in modern browsers). // Fallback to old behavior (often blocked in modern browsers).
var url = normalizeToFileUrl(raw); var url = OpenService.normalizeToFileUrl(raw);
if (!url) return; if (!url) return;
console.warn('Local client service failed, falling back to file:// navigation:', err); console.warn('Local client service failed, falling back to file:// navigation:', err);
try { try {
@@ -624,6 +648,6 @@
// Initialize // Initialize
// ============================================================ // ============================================================
document.addEventListener('click', onClick, false); document.addEventListener('click', onClick, false);
document.addEventListener('DOMContentLoaded', initGalleryThumbs, false); document.addEventListener('DOMContentLoaded', GalleryThumbnails.init, false);
document.addEventListener('DOMContentLoaded', initScratchpads, false); document.addEventListener('DOMContentLoaded', Scratchpads.init, false);
})(); })();