169 lines
4.7 KiB
JavaScript
169 lines
4.7 KiB
JavaScript
/* global window, document */
|
|
|
|
(function () {
|
|
'use strict';
|
|
|
|
function initGalleryThumbs() {
|
|
var imgs = document.querySelectorAll('div.luxtools-gallery img[data-thumb-src]');
|
|
if (!imgs || !imgs.length) return;
|
|
|
|
function loadThumb(img) {
|
|
var src = img.getAttribute('data-thumb-src') || '';
|
|
if (!src) return;
|
|
if (img.getAttribute('data-thumb-loading') === '1') return;
|
|
img.setAttribute('data-thumb-loading', '1');
|
|
|
|
var pre = new window.Image();
|
|
pre.onload = function () {
|
|
img.src = src;
|
|
img.removeAttribute('data-thumb-src');
|
|
img.removeAttribute('data-thumb-loading');
|
|
};
|
|
pre.onerror = function () {
|
|
img.removeAttribute('data-thumb-loading');
|
|
};
|
|
pre.src = src;
|
|
}
|
|
|
|
if ('IntersectionObserver' in window) {
|
|
var io = new window.IntersectionObserver(function (entries) {
|
|
entries.forEach(function (entry) {
|
|
if (!entry.isIntersecting) return;
|
|
loadThumb(entry.target);
|
|
io.unobserve(entry.target);
|
|
});
|
|
}, { rootMargin: '200px' });
|
|
|
|
imgs.forEach(function (img) { io.observe(img); });
|
|
} else {
|
|
// Fallback: load soon after initial render
|
|
window.setTimeout(function () {
|
|
imgs.forEach(loadThumb);
|
|
}, 0);
|
|
}
|
|
}
|
|
|
|
function getServiceUrl(el) {
|
|
var url = el.getAttribute('data-service-url') || '';
|
|
url = (url || '').trim();
|
|
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
|
|
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;
|
|
}
|
|
|
|
function findOpenElement(target) {
|
|
var el = target;
|
|
while (el && el !== document) {
|
|
if (el.classList && el.classList.contains('luxtools-open')) return el;
|
|
el = el.parentNode;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function onClick(event) {
|
|
var el = findOpenElement(event.target);
|
|
if (!el) return;
|
|
|
|
// {{open>...}} renders as a link; avoid jumping to '#'.
|
|
if (el.tagName && el.tagName.toLowerCase() === 'a') {
|
|
event.preventDefault();
|
|
}
|
|
|
|
var raw = el.getAttribute('data-path') || '';
|
|
if (!raw) return;
|
|
|
|
// Prefer local client service.
|
|
openViaService(el, raw)
|
|
.catch(function (err) {
|
|
// If the browser blocks the request before it reaches localhost (mixed-content,
|
|
// extensions, stricter CORS handling), fall back to a no-CORS GET ping.
|
|
pingOpenViaImage(el, raw);
|
|
|
|
// Fallback to old behavior (often blocked in modern browsers).
|
|
var url = normalizeToFileUrl(raw);
|
|
if (!url) return;
|
|
console.warn('Local client service failed, falling back to file:// navigation:', err);
|
|
try {
|
|
window.open(url, '_blank', 'noopener');
|
|
} catch (e) {
|
|
try {
|
|
window.location.href = url;
|
|
} catch (e2) {
|
|
console.error('Failed to open file URL:', e2);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
document.addEventListener('click', onClick, false);
|
|
document.addEventListener('DOMContentLoaded', initGalleryThumbs, false);
|
|
})();
|