Add CI/CD pipelines, NSIS installer, ES module bundling, and ruff linting
- Add Gitea Actions workflows: test.yml (lint + test on push/PR) and release.yml (build + NSIS installer + upload on v* tags) - Add NSIS installer with optional desktop shortcut and auto-start - Add esbuild bundler: ES module migration with IIFE bundle output - Add build-dist-windows.sh for cross-building Windows distribution - Fix all ruff lint errors (import sorting, unused imports, line length) - Remove redundant scripts (start-server.bat, stop-server.bat, start-server-background.vbs) - Update CLAUDE.md with CI/CD and release documentation
This commit is contained in:
@@ -2,6 +2,11 @@
|
||||
// Media Browser: Navigation, rendering, search, pagination
|
||||
// ============================================================
|
||||
|
||||
import {
|
||||
t, showToast, escapeHtml, closeDialog,
|
||||
SEARCH_DEBOUNCE_MS, EMPTY_SVG_FILE, EMPTY_SVG_FOLDER, emptyStateHtml,
|
||||
} from './core.js';
|
||||
|
||||
// Browser state
|
||||
let currentFolderId = null;
|
||||
let currentPath = '';
|
||||
@@ -13,11 +18,11 @@ let viewMode = localStorage.getItem('mediaBrowser.viewMode') || 'grid';
|
||||
let cachedItems = null;
|
||||
let browserSearchTerm = '';
|
||||
let browserSearchTimer = null;
|
||||
const thumbnailCache = new Map();
|
||||
export const thumbnailCache = new Map();
|
||||
const THUMBNAIL_CACHE_MAX = 200;
|
||||
|
||||
// Load media folders on page load
|
||||
async function loadMediaFolders() {
|
||||
export async function loadMediaFolders() {
|
||||
try {
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
if (!token) {
|
||||
@@ -169,11 +174,11 @@ async function browsePath(folderId, path, offset = 0, nocache = false) {
|
||||
}
|
||||
}
|
||||
|
||||
function renderBreadcrumbs(currentPath, parentPath) {
|
||||
function renderBreadcrumbs(currentPathStr, parentPath) {
|
||||
const breadcrumb = document.getElementById('breadcrumb');
|
||||
breadcrumb.innerHTML = '';
|
||||
|
||||
const parts = (currentPath || '').split('/').filter(p => p);
|
||||
const parts = (currentPathStr || '').split('/').filter(p => p);
|
||||
let path = '/';
|
||||
|
||||
// Home link (back to folder list)
|
||||
@@ -373,10 +378,10 @@ function renderBrowserGrid(items, container) {
|
||||
// Lazy load thumbnail
|
||||
loadThumbnail(thumbnail, item.name);
|
||||
} else {
|
||||
const icon = document.createElement('div');
|
||||
icon.className = 'browser-icon';
|
||||
icon.textContent = getFileIcon(item.type);
|
||||
thumbWrapper.appendChild(icon);
|
||||
const iconEl = document.createElement('div');
|
||||
iconEl.className = 'browser-icon';
|
||||
iconEl.textContent = getFileIcon(item.type);
|
||||
thumbWrapper.appendChild(iconEl);
|
||||
}
|
||||
|
||||
// Play overlay for media files
|
||||
@@ -527,11 +532,10 @@ async function loadThumbnail(imgElement, fileName) {
|
||||
};
|
||||
|
||||
// Revoke previous blob URL if not managed by cache
|
||||
// (Cache is keyed by path, so check values)
|
||||
if (imgElement.src && imgElement.src.startsWith('blob:')) {
|
||||
let isCached = false;
|
||||
for (const url of thumbnailCache.values()) {
|
||||
if (url === imgElement.src) { isCached = true; break; }
|
||||
for (const cachedUrl of thumbnailCache.values()) {
|
||||
if (cachedUrl === imgElement.src) { isCached = true; break; }
|
||||
}
|
||||
if (!isCached) URL.revokeObjectURL(imgElement.src);
|
||||
}
|
||||
@@ -544,10 +548,10 @@ async function loadThumbnail(imgElement, fileName) {
|
||||
if (isList) {
|
||||
parent.textContent = '\u{1F3B5}';
|
||||
} else {
|
||||
const icon = document.createElement('div');
|
||||
icon.className = 'browser-icon';
|
||||
icon.textContent = '\u{1F3B5}';
|
||||
parent.insertBefore(icon, parent.firstChild);
|
||||
const iconEl = document.createElement('div');
|
||||
iconEl.className = 'browser-icon';
|
||||
iconEl.textContent = '\u{1F3B5}';
|
||||
parent.insertBefore(iconEl, parent.firstChild);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -600,7 +604,7 @@ async function playMediaFile(fileName) {
|
||||
}
|
||||
}
|
||||
|
||||
async function playAllFolder() {
|
||||
export async function playAllFolder() {
|
||||
if (playInProgress) return;
|
||||
playInProgress = true;
|
||||
const btn = document.getElementById('playAllBtn');
|
||||
@@ -634,7 +638,7 @@ async function playAllFolder() {
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadFile(fileName, event) {
|
||||
export async function downloadFile(fileName, event) {
|
||||
if (event) event.stopPropagation();
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
if (!token) return;
|
||||
@@ -699,19 +703,19 @@ function renderPagination() {
|
||||
nextBtn.disabled = currentPage === totalPages;
|
||||
}
|
||||
|
||||
function previousPage() {
|
||||
export function previousPage() {
|
||||
if (currentOffset >= itemsPerPage) {
|
||||
browsePath(currentFolderId, currentPath, currentOffset - itemsPerPage);
|
||||
}
|
||||
}
|
||||
|
||||
function nextPage() {
|
||||
export function nextPage() {
|
||||
if (currentOffset + itemsPerPage < totalItems) {
|
||||
browsePath(currentFolderId, currentPath, currentOffset + itemsPerPage);
|
||||
}
|
||||
}
|
||||
|
||||
function refreshBrowser() {
|
||||
export function refreshBrowser() {
|
||||
if (currentFolderId) {
|
||||
browsePath(currentFolderId, currentPath, currentOffset, true);
|
||||
} else {
|
||||
@@ -720,7 +724,7 @@ function refreshBrowser() {
|
||||
}
|
||||
|
||||
// Browser search
|
||||
function onBrowserSearch() {
|
||||
export function onBrowserSearch() {
|
||||
const input = document.getElementById('browserSearchInput');
|
||||
const clearBtn = document.getElementById('browserSearchClear');
|
||||
const term = input.value.trim();
|
||||
@@ -735,7 +739,7 @@ function onBrowserSearch() {
|
||||
}, SEARCH_DEBOUNCE_MS);
|
||||
}
|
||||
|
||||
function clearBrowserSearch() {
|
||||
export function clearBrowserSearch() {
|
||||
const input = document.getElementById('browserSearchInput');
|
||||
input.value = '';
|
||||
document.getElementById('browserSearchClear').style.display = 'none';
|
||||
@@ -768,7 +772,7 @@ function showBrowserSearch(visible) {
|
||||
}
|
||||
}
|
||||
|
||||
function setViewMode(mode) {
|
||||
export function setViewMode(mode) {
|
||||
if (mode === viewMode) return;
|
||||
viewMode = mode;
|
||||
localStorage.setItem('mediaBrowser.viewMode', mode);
|
||||
@@ -786,7 +790,7 @@ function setViewMode(mode) {
|
||||
}
|
||||
}
|
||||
|
||||
function onItemsPerPageChanged() {
|
||||
export function onItemsPerPageChanged() {
|
||||
const select = document.getElementById('itemsPerPageSelect');
|
||||
itemsPerPage = parseInt(select.value);
|
||||
localStorage.setItem('mediaBrowser.itemsPerPage', itemsPerPage);
|
||||
@@ -798,7 +802,7 @@ function onItemsPerPageChanged() {
|
||||
}
|
||||
}
|
||||
|
||||
function goToPage() {
|
||||
export function goToPage() {
|
||||
const pageInput = document.getElementById('pageInput');
|
||||
const totalPages = Math.ceil(totalItems / itemsPerPage);
|
||||
let page = parseInt(pageInput.value);
|
||||
@@ -813,7 +817,7 @@ function goToPage() {
|
||||
}
|
||||
}
|
||||
|
||||
function initBrowserToolbar() {
|
||||
export function initBrowserToolbar() {
|
||||
// Restore view mode
|
||||
const savedViewMode = localStorage.getItem('mediaBrowser.viewMode') || 'grid';
|
||||
viewMode = savedViewMode;
|
||||
@@ -865,18 +869,16 @@ function loadLastBrowserPath() {
|
||||
}
|
||||
|
||||
// Folder Management
|
||||
function showManageFoldersDialog() {
|
||||
export function showManageFoldersDialog() {
|
||||
// TODO: Implement folder management UI
|
||||
// For now, show a simple alert
|
||||
showToast(t('browser.manage_folders_hint'), 'info');
|
||||
}
|
||||
|
||||
function closeFolderDialog() {
|
||||
export function closeFolderDialog() {
|
||||
closeDialog(document.getElementById('folderDialog'));
|
||||
}
|
||||
|
||||
async function saveFolder(event) {
|
||||
export async function saveFolder(event) {
|
||||
event.preventDefault();
|
||||
// TODO: Implement folder save functionality
|
||||
closeFolderDialog();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user