arama özelliği eklendi
This commit is contained in:
@@ -1,11 +1,21 @@
|
|||||||
<script>
|
<script>
|
||||||
import { createEventDispatcher, onMount } from "svelte";
|
import { createEventDispatcher } from "svelte";
|
||||||
const dispatch = createEventDispatcher();
|
import {
|
||||||
|
activePlaceholder,
|
||||||
|
activeSearchTerm,
|
||||||
|
updateSearchTerm
|
||||||
|
} from "../stores/searchStore.js";
|
||||||
|
|
||||||
let search = "";
|
const dispatch = createEventDispatcher();
|
||||||
export let placeholder = "Search files...";
|
export let placeholder = "";
|
||||||
|
|
||||||
const onToggle = () => dispatch("toggleMenu");
|
const onToggle = () => dispatch("toggleMenu");
|
||||||
|
|
||||||
|
function handleInput(event) {
|
||||||
|
updateSearchTerm(event.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
$: resolvedPlaceholder = placeholder || $activePlaceholder;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="topbar">
|
<div class="topbar">
|
||||||
@@ -20,7 +30,12 @@
|
|||||||
|
|
||||||
<div class="search">
|
<div class="search">
|
||||||
<i class="fa-solid fa-magnifying-glass"></i>
|
<i class="fa-solid fa-magnifying-glass"></i>
|
||||||
<input placeholder={placeholder} bind:value={search} />
|
<input
|
||||||
|
type="search"
|
||||||
|
placeholder={resolvedPlaceholder}
|
||||||
|
value={$activeSearchTerm}
|
||||||
|
on:input={handleInput}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,11 @@
|
|||||||
import { cleanFileName, extractTitleAndYear } from "../utils/filename.js";
|
import { cleanFileName, extractTitleAndYear } from "../utils/filename.js";
|
||||||
import { refreshMovieCount } from "../stores/movieStore.js";
|
import { refreshMovieCount } from "../stores/movieStore.js";
|
||||||
import { refreshTvShowCount } from "../stores/tvStore.js";
|
import { refreshTvShowCount } from "../stores/tvStore.js";
|
||||||
|
import {
|
||||||
|
activeSearchTerm,
|
||||||
|
setSearchScope,
|
||||||
|
clearSearch
|
||||||
|
} from "../stores/searchStore.js";
|
||||||
const HIDDEN_ROOT_REGEX = /^\d{10,}$/;
|
const HIDDEN_ROOT_REGEX = /^\d{10,}$/;
|
||||||
const FOLDER_ICON_PATH = "/folder.svg";
|
const FOLDER_ICON_PATH = "/folder.svg";
|
||||||
const MAX_TRAIL_SEGMENTS = 3;
|
const MAX_TRAIL_SEGMENTS = 3;
|
||||||
@@ -13,6 +18,7 @@
|
|||||||
let visibleFolders = [];
|
let visibleFolders = [];
|
||||||
let visibleFiles = [];
|
let visibleFiles = [];
|
||||||
let visibleEntries = [];
|
let visibleEntries = [];
|
||||||
|
let renderedEntries = [];
|
||||||
let allDirectories = [];
|
let allDirectories = [];
|
||||||
let breadcrumbs = [];
|
let breadcrumbs = [];
|
||||||
let currentFileScope = [];
|
let currentFileScope = [];
|
||||||
@@ -21,6 +27,8 @@
|
|||||||
let draggingItem = null;
|
let draggingItem = null;
|
||||||
let dragOverItem = null;
|
let dragOverItem = null;
|
||||||
let lastDragPath = "";
|
let lastDragPath = "";
|
||||||
|
let searchTerm = "";
|
||||||
|
let hasSearch = false;
|
||||||
|
|
||||||
const normalizePath = (value) => {
|
const normalizePath = (value) => {
|
||||||
if (!value) return "";
|
if (!value) return "";
|
||||||
@@ -33,6 +41,22 @@
|
|||||||
/safari/i.test(navigator.userAgent || "") &&
|
/safari/i.test(navigator.userAgent || "") &&
|
||||||
!/chrome|crios|android/i.test(navigator.userAgent || "");
|
!/chrome|crios|android/i.test(navigator.userAgent || "");
|
||||||
|
|
||||||
|
function filterEntriesBySearch(entries, term) {
|
||||||
|
const query = term.trim().toLowerCase();
|
||||||
|
if (!query) return entries;
|
||||||
|
return entries.filter((entry) => {
|
||||||
|
const labels = [
|
||||||
|
entry.displayName,
|
||||||
|
entry.displayPath,
|
||||||
|
entry.name
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(" ")
|
||||||
|
.toLowerCase();
|
||||||
|
return labels.includes(query);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const shouldHideRootSegment = (segment) =>
|
const shouldHideRootSegment = (segment) =>
|
||||||
segment && (HIDDEN_ROOT_REGEX.test(segment) || segment === "downloads");
|
segment && (HIDDEN_ROOT_REGEX.test(segment) || segment === "downloads");
|
||||||
|
|
||||||
@@ -157,6 +181,10 @@
|
|||||||
customOrder.set(key, filteredOrder);
|
customOrder.set(key, filteredOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: searchTerm = $activeSearchTerm;
|
||||||
|
$: hasSearch = searchTerm.trim().length > 0;
|
||||||
|
$: renderedEntries = filterEntriesBySearch(visibleEntries, searchTerm);
|
||||||
|
|
||||||
function computeBreadcrumbs(path) {
|
function computeBreadcrumbs(path) {
|
||||||
const segments = path ? path.split("/").filter(Boolean) : [];
|
const segments = path ? path.split("/").filter(Boolean) : [];
|
||||||
const crumbs = [{ label: "Home", path: "" }];
|
const crumbs = [{ label: "Home", path: "" }];
|
||||||
@@ -320,11 +348,10 @@
|
|||||||
}
|
}
|
||||||
let selectedItems = new Set();
|
let selectedItems = new Set();
|
||||||
let allSelected = false;
|
let allSelected = false;
|
||||||
function syncSelectionState() {
|
$: {
|
||||||
const keys = visibleEntries.map((entry) => entry.name).filter(Boolean);
|
const keys = renderedEntries.map((entry) => entry.name).filter(Boolean);
|
||||||
allSelected = keys.length > 0 && keys.every((key) => selectedItems.has(key));
|
allSelected = keys.length > 0 && keys.every((key) => selectedItems.has(key));
|
||||||
}
|
}
|
||||||
$: syncSelectionState();
|
|
||||||
let pendingPlayTarget = null;
|
let pendingPlayTarget = null;
|
||||||
let activeMenu = null; // Aktif menü öğesi
|
let activeMenu = null; // Aktif menü öğesi
|
||||||
let menuPosition = { top: 0, left: 0 }; // Menü pozisyonu
|
let menuPosition = { top: 0, left: 0 }; // Menü pozisyonu
|
||||||
@@ -485,7 +512,7 @@
|
|||||||
if (allSelected) {
|
if (allSelected) {
|
||||||
selectedItems = new Set();
|
selectedItems = new Set();
|
||||||
} else {
|
} else {
|
||||||
const keys = visibleEntries.map((entry) => entry.name).filter(Boolean);
|
const keys = renderedEntries.map((entry) => entry.name).filter(Boolean);
|
||||||
selectedItems = new Set(keys);
|
selectedItems = new Set(keys);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -624,12 +651,19 @@
|
|||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const hadSearch = searchTerm.trim().length > 0;
|
||||||
|
const nextSearchTerm = hadSearch ? "" : searchTerm;
|
||||||
currentPath = normalized;
|
currentPath = normalized;
|
||||||
currentOriginalPath = normalizedOriginal;
|
currentOriginalPath = normalizedOriginal;
|
||||||
|
if (hadSearch) {
|
||||||
|
clearSearch("files");
|
||||||
|
}
|
||||||
selectedItems = new Set();
|
selectedItems = new Set();
|
||||||
activeMenu = null;
|
activeMenu = null;
|
||||||
if (isCreatingFolder) cancelCreateFolder();
|
if (isCreatingFolder) cancelCreateFolder();
|
||||||
updateUrlPath(normalized, normalizedOriginal, { replace });
|
updateUrlPath(normalized, normalizedOriginal, { replace });
|
||||||
|
updateVisibleState(files, normalized);
|
||||||
|
renderedEntries = filterEntriesBySearch(visibleEntries, nextSearchTerm);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleEntryClick(entry) {
|
function handleEntryClick(entry) {
|
||||||
@@ -1288,6 +1322,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
setSearchScope("files");
|
||||||
await loadFiles(); // önce dosyaları getir
|
await loadFiles(); // önce dosyaları getir
|
||||||
const token = localStorage.getItem("token");
|
const token = localStorage.getItem("token");
|
||||||
const wsUrl = `${API.replace("http", "ws")}?token=${token}`;
|
const wsUrl = `${API.replace("http", "ws")}?token=${token}`;
|
||||||
@@ -1565,10 +1600,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
{#if visibleEntries.length > 0 && selectedItems.size > 0}
|
{#if renderedEntries.length > 0 && selectedItems.size > 0}
|
||||||
<span class="selection-count">{selectedItems.size} öğe seçildi</span>
|
<span class="selection-count">{selectedItems.size} öğe seçildi</span>
|
||||||
{/if}
|
{/if}
|
||||||
{#if visibleEntries.length > 0 && selectedItems.size > 0}
|
{#if renderedEntries.length > 0 && selectedItems.size > 0}
|
||||||
<button
|
<button
|
||||||
class="select-all-btn"
|
class="select-all-btn"
|
||||||
type="button"
|
type="button"
|
||||||
@@ -1603,10 +1638,16 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if visibleEntries.length === 0 && !isCreatingFolder}
|
{#if renderedEntries.length === 0 && !isCreatingFolder}
|
||||||
<div class="empty">
|
<div class="empty">
|
||||||
<div style="font-size:42px"><i class="fa-solid fa-folder-open"></i></div>
|
<div style="font-size:42px"><i class="fa-solid fa-folder-open"></i></div>
|
||||||
<div style="font-weight:700">No media found</div>
|
<div style="font-weight:700">
|
||||||
|
{#if hasSearch}
|
||||||
|
Aramanla eşleşen öğe bulunamadı
|
||||||
|
{:else}
|
||||||
|
No media found
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div
|
<div
|
||||||
@@ -1633,7 +1674,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#each visibleEntries as entry (entry.name)}
|
{#each renderedEntries as entry (entry.name)}
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
import { API, apiFetch } from "../utils/api.js";
|
import { API, apiFetch } from "../utils/api.js";
|
||||||
import { cleanFileName } from "../utils/filename.js";
|
import { cleanFileName } from "../utils/filename.js";
|
||||||
import { movieCount } from "../stores/movieStore.js";
|
import { movieCount } from "../stores/movieStore.js";
|
||||||
|
import {
|
||||||
|
activeSearchTerm,
|
||||||
|
setSearchScope
|
||||||
|
} from "../stores/searchStore.js";
|
||||||
|
|
||||||
let movies = [];
|
let movies = [];
|
||||||
let loading = true;
|
let loading = true;
|
||||||
@@ -35,6 +39,9 @@ let subtitleURL = null;
|
|||||||
let subtitleLabel = "Custom Subtitles";
|
let subtitleLabel = "Custom Subtitles";
|
||||||
$: selectedName = selectedVideo?.name ?? "";
|
$: selectedName = selectedVideo?.name ?? "";
|
||||||
$: encName = selectedName ? encodeURIComponent(selectedName) : "";
|
$: encName = selectedName ? encodeURIComponent(selectedName) : "";
|
||||||
|
let searchTerm = "";
|
||||||
|
let hasSearch = false;
|
||||||
|
let filteredMovies = [];
|
||||||
|
|
||||||
function runtimeToText(runtime) {
|
function runtimeToText(runtime) {
|
||||||
if (!runtime || Number.isNaN(runtime)) return null;
|
if (!runtime || Number.isNaN(runtime)) return null;
|
||||||
@@ -119,6 +126,25 @@ $: if (showPlayerModal && selectedVideo) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: searchTerm = $activeSearchTerm;
|
||||||
|
$: hasSearch = searchTerm.trim().length > 0;
|
||||||
|
$: filteredMovies = (() => {
|
||||||
|
const query = searchTerm.trim().toLowerCase();
|
||||||
|
if (!query) return movies;
|
||||||
|
return movies.filter((movie) => {
|
||||||
|
const fields = [
|
||||||
|
movie.title,
|
||||||
|
movie.originalTitle,
|
||||||
|
movie.metadata?.matched_title,
|
||||||
|
movie.metadata?.original_title,
|
||||||
|
movie.folder
|
||||||
|
];
|
||||||
|
return fields
|
||||||
|
.filter(Boolean)
|
||||||
|
.some((value) => String(value).toLowerCase().includes(query));
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
async function handlePlay(movie) {
|
async function handlePlay(movie) {
|
||||||
if (!movie) return;
|
if (!movie) return;
|
||||||
closeMovie();
|
closeMovie();
|
||||||
@@ -335,6 +361,7 @@ async function loadMovies() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
setSearchScope("movies");
|
||||||
loadMovies();
|
loadMovies();
|
||||||
const handleKey = (event) => {
|
const handleKey = (event) => {
|
||||||
if (!showPlayerModal) return;
|
if (!showPlayerModal) return;
|
||||||
@@ -381,9 +408,11 @@ async function loadMovies() {
|
|||||||
<div class="state-placeholder error">{error}</div>
|
<div class="state-placeholder error">{error}</div>
|
||||||
{:else if movies.length === 0}
|
{:else if movies.length === 0}
|
||||||
<div class="state-placeholder">No movie metadata found yet.</div>
|
<div class="state-placeholder">No movie metadata found yet.</div>
|
||||||
|
{:else if hasSearch && filteredMovies.length === 0}
|
||||||
|
<div class="state-placeholder">Aramanıza uyan film bulunamadı.</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="movies-grid">
|
<div class="movies-grid">
|
||||||
{#each movies as movie}
|
{#each filteredMovies as movie}
|
||||||
<div class="movie-card" on:click={() => openMovie(movie)}>
|
<div class="movie-card" on:click={() => openMovie(movie)}>
|
||||||
{#if movie.poster}
|
{#if movie.poster}
|
||||||
<div class="poster-wrapper">
|
<div class="poster-wrapper">
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
import { API, apiFetch } from "../utils/api.js";
|
import { API, apiFetch } from "../utils/api.js";
|
||||||
import { cleanFileName } from "../utils/filename.js";
|
import { cleanFileName } from "../utils/filename.js";
|
||||||
import { tvShowCount } from "../stores/tvStore.js";
|
import { tvShowCount } from "../stores/tvStore.js";
|
||||||
|
import {
|
||||||
|
activeSearchTerm,
|
||||||
|
setSearchScope
|
||||||
|
} from "../stores/searchStore.js";
|
||||||
|
|
||||||
let shows = [];
|
let shows = [];
|
||||||
let loading = true;
|
let loading = true;
|
||||||
@@ -28,6 +32,9 @@ let seasonPlaylist = [];
|
|||||||
let seasonPlaylistIndex = -1;
|
let seasonPlaylistIndex = -1;
|
||||||
let canPlayPrev = false;
|
let canPlayPrev = false;
|
||||||
let canPlayNext = false;
|
let canPlayNext = false;
|
||||||
|
let searchTerm = "";
|
||||||
|
let hasSearch = false;
|
||||||
|
let filteredShows = [];
|
||||||
|
|
||||||
function runtimeToText(runtime) {
|
function runtimeToText(runtime) {
|
||||||
if (!runtime || Number.isNaN(runtime)) return null;
|
if (!runtime || Number.isNaN(runtime)) return null;
|
||||||
@@ -190,6 +197,25 @@ let canPlayNext = false;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: searchTerm = $activeSearchTerm;
|
||||||
|
$: hasSearch = searchTerm.trim().length > 0;
|
||||||
|
$: filteredShows = (() => {
|
||||||
|
const query = searchTerm.trim().toLowerCase();
|
||||||
|
if (!query) return shows;
|
||||||
|
return shows.filter((show) => {
|
||||||
|
const fields = [
|
||||||
|
show.title,
|
||||||
|
show.originalTitle,
|
||||||
|
show.metadata?.matched_title,
|
||||||
|
show.metadata?.original_name,
|
||||||
|
show.folder
|
||||||
|
];
|
||||||
|
return fields
|
||||||
|
.filter(Boolean)
|
||||||
|
.some((value) => String(value).toLowerCase().includes(query));
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
async function refreshShows() {
|
async function refreshShows() {
|
||||||
try {
|
try {
|
||||||
refreshing = true;
|
refreshing = true;
|
||||||
@@ -542,6 +568,7 @@ async function openVideoAtIndex(index) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
setSearchScope("tv");
|
||||||
loadShows();
|
loadShows();
|
||||||
const handleKey = (event) => {
|
const handleKey = (event) => {
|
||||||
if (!showPlayerModal) return;
|
if (!showPlayerModal) return;
|
||||||
@@ -588,9 +615,11 @@ async function openVideoAtIndex(index) {
|
|||||||
<div class="state-placeholder error">{error}</div>
|
<div class="state-placeholder error">{error}</div>
|
||||||
{:else if shows.length === 0}
|
{:else if shows.length === 0}
|
||||||
<div class="state-placeholder">No TV metadata found yet.</div>
|
<div class="state-placeholder">No TV metadata found yet.</div>
|
||||||
|
{:else if hasSearch && filteredShows.length === 0}
|
||||||
|
<div class="state-placeholder">Aramanıza uyan dizi bulunamadı.</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="tv-grid">
|
<div class="tv-grid">
|
||||||
{#each shows as show}
|
{#each filteredShows as show}
|
||||||
<div class="tv-card" on:click={() => openShow(show)}>
|
<div class="tv-card" on:click={() => openShow(show)}>
|
||||||
{#if show.poster}
|
{#if show.poster}
|
||||||
<div class="poster-wrapper">
|
<div class="poster-wrapper">
|
||||||
|
|||||||
80
client/src/stores/searchStore.js
Normal file
80
client/src/stores/searchStore.js
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import { derived, writable } from "svelte/store";
|
||||||
|
|
||||||
|
const KNOWN_SCOPES = new Set(["files", "movies", "tv"]);
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
scope: "files",
|
||||||
|
terms: {
|
||||||
|
files: "",
|
||||||
|
movies: "",
|
||||||
|
tv: ""
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const searchState = writable(initialState);
|
||||||
|
|
||||||
|
export const activeScope = derived(searchState, ($state) => $state.scope);
|
||||||
|
|
||||||
|
export const activeSearchTerm = derived(
|
||||||
|
searchState,
|
||||||
|
($state) => $state.terms[$state.scope] || ""
|
||||||
|
);
|
||||||
|
|
||||||
|
const PLACEHOLDERS = {
|
||||||
|
files: "Dosya ara...",
|
||||||
|
movies: "Film ara...",
|
||||||
|
tv: "Dizi ara..."
|
||||||
|
};
|
||||||
|
|
||||||
|
export const activePlaceholder = derived(
|
||||||
|
searchState,
|
||||||
|
($state) => PLACEHOLDERS[$state.scope] || "Ara..."
|
||||||
|
);
|
||||||
|
|
||||||
|
export function setSearchScope(scope) {
|
||||||
|
const normalized = KNOWN_SCOPES.has(scope) ? scope : "files";
|
||||||
|
searchState.update((state) => {
|
||||||
|
const hasScope = Object.prototype.hasOwnProperty.call(state.terms, normalized);
|
||||||
|
const terms = hasScope ? state.terms : { ...state.terms, [normalized]: "" };
|
||||||
|
if (state.scope === normalized && terms === state.terms) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
scope: normalized,
|
||||||
|
terms
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateSearchTerm(term) {
|
||||||
|
searchState.update((state) => {
|
||||||
|
const scope = state.scope;
|
||||||
|
const nextTerms = {
|
||||||
|
...state.terms,
|
||||||
|
[scope]: term
|
||||||
|
};
|
||||||
|
if (nextTerms[scope] === state.terms[scope]) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
scope,
|
||||||
|
terms: nextTerms
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearSearch(scope) {
|
||||||
|
searchState.update((state) => {
|
||||||
|
const targetScope = scope && KNOWN_SCOPES.has(scope) ? scope : state.scope;
|
||||||
|
if ((state.terms[targetScope] || "") === "") {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
scope: state.scope,
|
||||||
|
terms: {
|
||||||
|
...state.terms,
|
||||||
|
[targetScope]: ""
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -2811,23 +2811,25 @@ app.delete("/api/file", requireAuth, (req, res) => {
|
|||||||
if (!name) return false;
|
if (!name) return false;
|
||||||
if (name === INFO_FILENAME) return false;
|
if (name === INFO_FILENAME) return false;
|
||||||
if (name.startsWith(".")) return false;
|
if (name.startsWith(".")) return false;
|
||||||
const full = path.join(rootDir, name);
|
|
||||||
try {
|
|
||||||
const stat = fs.statSync(full);
|
|
||||||
if (stat.isDirectory()) {
|
|
||||||
const subItems = fs.readdirSync(full);
|
|
||||||
return subItems.some((entry) => !entry.startsWith("."));
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (meaningful.length === 0 || stats?.isDirectory?.()) {
|
pruneInfoEntry(folderId, relWithinRoot);
|
||||||
purgeRootFolder(folderId);
|
removeSeriesEpisode(folderId, relWithinRoot);
|
||||||
|
|
||||||
|
if (meaningful.length === 0) {
|
||||||
|
removeAllThumbnailsForRoot(folderId);
|
||||||
|
removeMovieData(folderId);
|
||||||
|
removeSeriesData(folderId);
|
||||||
|
const infoPath = path.join(rootDir, INFO_FILENAME);
|
||||||
|
if (fs.existsSync(infoPath)) {
|
||||||
|
try {
|
||||||
|
fs.rmSync(infoPath, { force: true });
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(`⚠️ info.json kaldırılamadı (${infoPath}): ${err.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
pruneInfoEntry(folderId, relWithinRoot);
|
|
||||||
const infoAfter = readInfoForRoot(folderId);
|
const infoAfter = readInfoForRoot(folderId);
|
||||||
const displayName = infoAfter?.name || folderId;
|
const displayName = infoAfter?.name || folderId;
|
||||||
const primaryVideo = infoAfter?.primaryVideoPath || guessPrimaryVideo(folderId);
|
const primaryVideo = infoAfter?.primaryVideoPath || guessPrimaryVideo(folderId);
|
||||||
@@ -2843,7 +2845,6 @@ app.delete("/api/file", requireAuth, (req, res) => {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
removeSeriesEpisode(folderId, relWithinRoot);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user