// -----------------------------
// Interactive FILMS Dashboard (v39: Ultra-Lean Memory Manager)
// -----------------------------
const favoriteActorsList = [
"Tom Hanks", "Leonardo DiCaprio", "Brad Pitt", "Sigourney Weaver",
"Anthony Hopkins", "Denzel Washington", "Meryl Streep", "Christian Bale"
];
const favoriteDirectorsList = [
"Steven Spielberg", "Christopher Nolan", "Quentin Tarantino",
"Martin Scorsese", "Stanley Kubrick", "Ridley Scott", "David Fincher"
];
const allMovies = dv.pages('"FILMS"').where(p => p.type === "film");
function toArray(val) { return (!val) ? [] : (Array.isArray(val) ? val : [val]); }
function cleanName(s) { return (!s) ? "" : String(s).replace(/^(person|actor|genre)\//, "").replace(/_/g, " "); }
function formatList(arr, max = 10) {
const arrSafe = toArray(arr);
if (arrSafe.length === 0) return "";
const cleaned = arrSafe.map(cleanName);
if (cleaned.length <= max) return cleaned.join(", ");
return cleaned.slice(0, max).join(", ") + ` <span style="color: var(--text-muted); font-size: 0.8em; font-style: italic; white-space: nowrap;">(+${cleaned.length - max})</span>`;
}
function truncateText(str, maxLength = 150) {
if (!str) return "";
let s = String(str);
if (s.length <= maxLength) return s;
return s.substring(0, maxLength).trim() + "...";
}
function formatMoney(amount) {
if (!amount || amount === 0) return "-";
if (amount >= 1000000000) return `$${(amount / 1000000000).toFixed(1)}B`;
if (amount >= 1000000) return `$${(amount / 1000000).toFixed(1)}M`;
return `$${amount.toLocaleString()}`;
}
const specialBadges = [
{ id: "owned", label: "Owned", icon: "πΏ", type: "prop" },
{ id: "fav", label: "Favorite", icon: "β€οΈ", type: "prop" },
{ id: "masterpiece", label: "Masterpiece", icon: "π", type: "prop" },
{ id: "mindfuck", label: "Mind-Bending", icon: "π§ ", type: "prop" },
{ id: "guilty", label: "Guilty Pleasure", icon: "π", type: "prop" },
{ id: "hot", label: "Hot", icon: "πΆοΈ", type: "prop" },
{ id: "extreme", label: "Controversial / Extreme", icon: "β’οΈ", type: "prop" },
{ id: "legendary", label: "Legendary Film", icon: "πΏ", type: "prop" },
{ id: "foreign", label: "Non-English Films", icon: "π", type: "computed" },
{ id: "reviewed", label: "My Reviews", icon: "π", type: "computed" }
];
const langBadgeData = [
{ label: "EN", icon: "πΊπΈ/π¬π§", matchNames: ["english"] },
{ label: "JP", icon: "π―π΅", matchNames: ["japanese", "nihongo"] },
{ label: "FR", icon: "π«π·", matchNames: ["french", "franΓ§ais"] },
{ label: "ES", icon: "πͺπΈ", matchNames: ["spanish", "espaΓ±ol"] },
{ label: "KO", icon: "π°π·", matchNames: ["korean", "hangul"] },
{ label: "RU", icon: "π·πΊ", matchNames: ["russian", "pΡΡΡΠΊΠΈΠΉ", "ΡΡΡΡΠΊΠΈΠΉ"] },
{ label: "SV", icon: "πΈπͺ", matchNames: ["swedish", "svenska"] }
];
const genreBadgeData = [
{ label: "Action", icon: "π₯" }, { label: "Comedy", icon: "π" }, { label: "Drama", icon: "π" },
{ label: "Horror", icon: "π¦" }, { label: "Romance", icon: "π" }, { label: "Sci-Fi", icon: "π½" }, { label: "Thriller", icon: "πͺ" }
];
const columnConfigs = [
{ id: "poster", name: "Poster", defaultOn: true, render: m => m.poster ? `<img src="${m.poster}" style="width:100px; height:auto; border-radius:4px;">` : "" },
{ id: "title", name: "Title", defaultOn: true, render: m => {
let specialIcons = "";
specialBadges.forEach(b => { if (b.type === "prop" && (m[b.id] === true || m[b.id] === "true")) specialIcons += `<span title="${b.label}" style="font-size: 1.1em; margin-right: 4px;">${b.icon}</span>`; });
if (m.spoken_languages.length > 0 && !m.spoken_languages.some(l => l.toLowerCase().includes("english"))) specialIcons += `<span title="Non-English Film" style="font-size: 1.1em; margin-right: 4px;">π</span>`;
let linksHtml = `<div style="display: flex; gap: 10px; margin-top: 4px; font-size: 0.8em; justify-content: flex-start;">`;
if (m.tmdb_url) linksHtml += `<a href="${m.tmdb_url}" target="_blank" style="text-decoration: none; color: var(--text-muted);">π¬ TMDb</a>`;
if (m.imdb_id) linksHtml += `<a href="https://www.imdb.com/title/${m.imdb_id}/" target="_blank" style="text-decoration: none; color: var(--text-muted);">ποΈ IMDb</a>`;
linksHtml += `</div>`;
let titleDisplay = m.title || "Untitled";
if (m.original_title && m.original_title !== m.title) titleDisplay += `<br><span style="font-size: 0.8em; color: var(--text-muted); font-weight: normal;">${m.original_title}</span>`;
return `<div style="min-width: 140px; white-space: normal; text-align: left;"><div style="font-weight: bold; font-size: 1.05em; display: flex; align-items: flex-start; flex-wrap: wrap; gap: 2px;">${specialIcons}<a class="internal-link" data-href="${m.path}" href="${m.path}">${titleDisplay}</a></div>${linksHtml}</div>`;
}},
{ id: "my_rating", name: "β", defaultOn: true, render: m => m.my_rating ? `<span style="color: var(--interactive-accent); font-weight: bold; font-size: 1.1em;">${m.my_rating}</span><span style="font-size: 0.8em; color: var(--text-muted);">/10</span>` : `<span style="color: var(--text-faint);">-</span>` },
{ id: "my_review", name: "My Notes", defaultOn: true, render: m => m.my_review ? `<div style="font-style: italic; color: var(--text-accent); background: var(--background-secondary-alt); padding: 6px 8px; border-left: 3px solid var(--interactive-accent); border-radius: 0 4px 4px 0; min-width: 180px; white-space: normal; font-size: 0.9em;">${m.my_review}</div>` : "" },
{ id: "year", name: "Year", defaultOn: true, render: m => m.year || "" },
{ id: "runtime", name: "Runtime", defaultOn: false, render: m => m.runtime ? `${m.runtime}m` : "" },
{ id: "budget", name: "Budget", defaultOn: false, render: m => `<span style="color: var(--text-muted);">${formatMoney(m.budget)}</span>` },
{ id: "revenue", name: "BO", defaultOn: false, render: m => `<span style="color: var(--text-accent); font-weight: bold;">${formatMoney(m.revenue)}</span>` },
{ id: "genres", name: "Genres", defaultOn: false, render: m => formatList(m.genres, 5) },
{ id: "actors", name: "Actors", defaultOn: true, render: m => `<div style="min-width: 140px; white-space: normal;">${formatList(m.actors, 10)}</div>` },
{ id: "director", name: "Dir", defaultOn: true, render: m => formatList(m.director, 5) },
{ id: "languages", name: "Lang", defaultOn: false, render: m => formatList(m.spoken_languages, 4) },
{ id: "rating", name: "Rtg", defaultOn: true, render: m => m.rating ? m.rating.toFixed(1) : "" },
{ id: "tagline", name: "Tagline", defaultOn: false, render: m => `<div style="min-width: 140px; font-style: italic; white-space: normal; font-size: 0.9em;">${truncateText(m.tagline, 150)}</div>` }
];
const ratingBuckets = [
{ id: "9", label: "9+" }, { id: "8", label: "8s" }, { id: "7", label: "7s" },
{ id: "6", label: "6s" }, { id: "5", label: "5s" }, { id: "under5", label: "<5" }, { id: "unrated", label: "Unrated" }
];
const savedState = JSON.parse(localStorage.getItem("film-dashboard-prefs")) || {};
let state = {
searchAll: savedState.searchAll || "", searchTitle: savedState.searchTitle || "", searchActor: savedState.searchActor || "",
searchDir: savedState.searchDir || "", searchGenre: savedState.searchGenre || "", searchLang: savedState.searchLang || "",
searchYear: savedState.searchYear || "", activeLang: savedState.activeLang || "", activeSpecial: savedState.activeSpecial || "",
activeGenre: savedState.activeGenre || "", activeFavActor: savedState.activeFavActor || "", activeFavDir: savedState.activeFavDir || "",
sortBy: savedState.sortBy || "year", sortDesc: savedState.sortDesc !== undefined ? savedState.sortDesc : true,
currentPage: 1, itemsPerPage: savedState.itemsPerPage || 10,
activeColumns: JSON.parse(localStorage.getItem("obsidian-films-dashboard-cols")) || columnConfigs.reduce((acc, col) => ({ ...acc, [col.id]: col.defaultOn }), {}),
activeRatings: JSON.parse(localStorage.getItem("obsidian-films-dashboard-ratings")) || ratingBuckets.reduce((acc, b) => ({ ...acc, [b.id]: true }), {})
};
function savePrefs() { localStorage.setItem("film-dashboard-prefs", JSON.stringify(state)); }
function saveColumnState() { localStorage.setItem("obsidian-films-dashboard-cols", JSON.stringify(state.activeColumns)); }
function saveRatingState() { localStorage.setItem("obsidian-films-dashboard-ratings", JSON.stringify(state.activeRatings)); }
const container = dv.el("div", "", { attr: { style: "display: flex; flex-direction: column; gap: 8px; width: 100%; font-size: 0.85em;" } });
function createSearchInput(parentDiv, placeholderText, stateKey, minWidth = "100px", maxWidth = "160px") {
const wrapper = parentDiv.createEl("div", { attr: { style: `display: flex; align-items: center; position: relative; flex: 1; min-width: ${minWidth}; max-width: ${maxWidth};` } });
const input = wrapper.createEl("input", { type: "text", placeholder: placeholderText, attr: { style: "width: 100%; padding: 4px 20px 4px 6px; border-radius: 4px; border: 1px solid var(--background-modifier-border); background: var(--background-primary); font-size: 1em;" } });
input.value = state[stateKey];
const clearBtn = wrapper.createEl("span", { text: "β", attr: { style: "position: absolute; right: 6px; cursor: pointer; color: var(--text-muted); font-size: 0.8em;" } });
let debounceTimer;
input.addEventListener("input", (e) => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => { state[stateKey] = e.target.value; state.currentPage = 1; savePrefs(); renderTable(); }, 250);
});
clearBtn.addEventListener("click", () => { input.value = ""; state[stateKey] = ""; state.currentPage = 1; savePrefs(); renderTable(); });
return input;
}
// --- UI SETUP ---
const topRow = container.createEl("div", { attr: { style: "display: flex; flex-wrap: wrap; gap: 8px; align-items: center; width: 100%;" } });
createSearchInput(topRow, "π Global...", "searchAll", "240px", "400px");
createSearchInput(topRow, "π
Year (e.g. 1972, 72, 89-90, 80s)...", "searchYear", "280px", "320px");
const sortContainer = topRow.createEl("div", { attr: { style: "display: flex; gap: 4px; align-items: center; background: var(--background-secondary); padding: 2px 6px; border-radius: 4px; border: 1px solid var(--background-modifier-border);" } });
const sortDropdown = sortContainer.createEl("select", { attr: { style: "background: transparent; border: none; cursor: pointer; color: var(--text-normal); font-size: 1em;" } });
["year", "rating", "my_rating", "title", "runtime", "budget", "revenue"].forEach(v => { sortDropdown.createEl("option", { value: v, text: v === "my_rating" ? "My Rating" : v.charAt(0).toUpperCase() + v.slice(1) }); });
sortDropdown.value = state.sortBy;
const sortOrderBtn = sortContainer.createEl("button", { text: state.sortDesc ? "β¬οΈ" : "β¬οΈ", attr: { style: "background: transparent; border: none; cursor: pointer; padding: 0 4px; font-size: 1em;" } });
sortDropdown.addEventListener("change", (e) => { state.sortBy = e.target.value; state.currentPage = 1; savePrefs(); renderTable(); });
sortOrderBtn.addEventListener("click", () => { state.sortDesc = !state.sortDesc; sortOrderBtn.innerText = state.sortDesc ? "β¬οΈ" : "β¬οΈ"; state.currentPage = 1; savePrefs(); renderTable(); });
const resetBtn = topRow.createEl("button", { text: "π Reset All", attr: { style: "margin-left: auto; cursor: pointer; padding: 4px 10px; border-radius: 4px; border: 1px solid var(--background-modifier-border); font-size: 0.9em;" } });
resetBtn.addEventListener("click", () => { localStorage.removeItem("film-dashboard-prefs"); localStorage.removeItem("obsidian-films-dashboard-cols"); localStorage.removeItem("obsidian-films-dashboard-ratings"); location.reload(); });
const badgeContainer = container.createEl("div", { attr: { style: "display: flex; gap: 6px; align-items: center; flex-wrap: wrap;" } });
specialBadges.forEach(b => {
const btn = badgeContainer.createEl("button", { text: b.icon, attr: { title: b.label, style: "font-size: 1.4em; padding: 0px 6px; border-radius: 8px; border: 1px solid var(--background-modifier-border); background: var(--background-secondary); cursor: pointer; transition: 0.2s;" } });
if (state.activeSpecial === b.id) { btn.style.background = "var(--interactive-accent)"; btn.style.borderColor = "var(--interactive-accent)"; }
btn.addEventListener("click", () => { state.activeSpecial = (state.activeSpecial === b.id) ? "" : b.id; state.currentPage = 1; savePrefs(); location.reload(); });
});
badgeContainer.createEl("span", { text: "|", attr: { style: "color: var(--background-modifier-border); margin: 0 2px;" } });
langBadgeData.forEach(b => {
const btn = badgeContainer.createEl("button", { text: b.icon, attr: { title: b.label, style: "font-size: 1.4em; padding: 0px 6px; border-radius: 8px; border: 1px solid var(--background-modifier-border); background: var(--background-secondary); cursor: pointer; transition: 0.2s;" } });
if (state.activeLang === b.label) { btn.style.background = "var(--interactive-accent)"; btn.style.borderColor = "var(--interactive-accent)"; }
btn.addEventListener("click", () => { state.activeLang = (state.activeLang === b.label) ? "" : b.label; state.currentPage = 1; savePrefs(); location.reload(); });
});
badgeContainer.createEl("span", { text: "|", attr: { style: "color: var(--background-modifier-border); margin: 0 2px;" } });
genreBadgeData.forEach(b => {
const btn = badgeContainer.createEl("button", { text: b.icon, attr: { title: b.label, style: "font-size: 1.4em; padding: 0px 6px; border-radius: 8px; border: 1px solid var(--background-modifier-border); background: var(--background-secondary); cursor: pointer; transition: 0.2s;" } });
if (state.activeGenre === b.label) { btn.style.background = "var(--interactive-accent)"; btn.style.borderColor = "var(--interactive-accent)"; }
btn.addEventListener("click", () => { state.activeGenre = (state.activeGenre === b.label) ? "" : b.label; state.currentPage = 1; savePrefs(); location.reload(); });
});
const specificRow = container.createEl("div", { attr: { style: "display: flex; flex-wrap: wrap; gap: 6px; width: 100%;" } });
createSearchInput(specificRow, "Title...", "searchTitle", "100px", "160px");
createSearchInput(specificRow, "Actor...", "searchActor", "100px", "160px");
createSearchInput(specificRow, "Dir...", "searchDir", "80px", "160px");
createSearchInput(specificRow, "Genre...", "searchGenre", "80px", "160px");
createSearchInput(specificRow, "Lang...", "searchLang", "60px", "160px");
const favDetails = container.createEl("details", { attr: { style: "background: var(--background-secondary); padding: 4px 10px; border-radius: 4px; border: 1px solid var(--background-modifier-border); margin-top: 4px;" }});
const favSummary = favDetails.createEl("summary", { text: "π Favorite Actors & Directors", attr: { style: "cursor: pointer; font-weight: bold; user-select: none;" }});
const favContent = favDetails.createEl("div", { attr: { style: "display: flex; flex-direction: column; gap: 8px; margin-top: 8px; padding-bottom: 4px;" }});
const favActorRow = favContent.createEl("div", { attr: { style: "display: flex; flex-wrap: wrap; gap: 6px; align-items: center;" }});
favActorRow.createEl("span", { text: "Actors:", attr: { style: "font-weight: bold; width: 65px; color: var(--text-muted);" }});
favoriteActorsList.forEach(actor => {
const btn = favActorRow.createEl("button", { text: actor, attr: { style: "padding: 2px 8px; border-radius: 12px; border: 1px solid var(--background-modifier-border); background: var(--background-primary); font-size: 0.9em; cursor: pointer;" }});
if (state.activeFavActor === actor) { btn.style.background = "var(--interactive-accent)"; btn.style.color = "var(--text-on-accent)"; }
btn.addEventListener("click", () => { state.activeFavActor = (state.activeFavActor === actor) ? "" : actor; state.currentPage = 1; savePrefs(); location.reload(); });
});
const favDirRow = favContent.createEl("div", { attr: { style: "display: flex; flex-wrap: wrap; gap: 6px; align-items: center;" }});
favDirRow.createEl("span", { text: "Directors:", attr: { style: "font-weight: bold; width: 65px; color: var(--text-muted);" }});
favoriteDirectorsList.forEach(dir => {
const btn = favDirRow.createEl("button", { text: dir, attr: { style: "padding: 2px 8px; border-radius: 12px; border: 1px solid var(--background-modifier-border); background: var(--background-primary); font-size: 0.9em; cursor: pointer;" }});
if (state.activeFavDir === dir) { btn.style.background = "var(--interactive-accent)"; btn.style.color = "var(--text-on-accent)"; }
btn.addEventListener("click", () => { state.activeFavDir = (state.activeFavDir === dir) ? "" : dir; state.currentPage = 1; savePrefs(); location.reload(); });
});
const togglesRow = container.createEl("div", { attr: { style: "display: flex; flex-wrap: wrap; justify-content: flex-start; gap: 10px; width: 100%; margin-top: 4px;" } });
const ratingWrapper = togglesRow.createEl("div", { attr: { style: "display: flex; flex-wrap: wrap; gap: 8px; padding: 4px 8px; background: var(--background-secondary); border-radius: 4px; border: 1px solid var(--background-modifier-border);" } });
ratingWrapper.createEl("span", { text: "β", attr: { style: "font-weight: bold; margin-right: 2px;" } });
ratingBuckets.forEach(bucket => {
const label = ratingWrapper.createEl("label", { attr: { style: "display: flex; align-items: center; gap: 3px; cursor: pointer;" } });
const checkbox = label.createEl("input", { type: "checkbox" });
checkbox.checked = state.activeRatings[bucket.id] !== undefined ? state.activeRatings[bucket.id] : true;
label.createEl("span", { text: bucket.label });
checkbox.addEventListener("change", (e) => { state.activeRatings[bucket.id] = e.target.checked; saveRatingState(); state.currentPage = 1; renderTable(); });
});
const colWrapper = togglesRow.createEl("div", { attr: { style: "display: flex; flex-wrap: wrap; gap: 8px; padding: 4px 8px; background: var(--background-secondary); border-radius: 4px; border: 1px solid var(--background-modifier-border);" } });
colWrapper.createEl("span", { text: "ποΈ Cols:", attr: { style: "font-weight: bold; margin-right: 2px;" } });
columnConfigs.forEach(col => {
const label = colWrapper.createEl("label", { attr: { style: "display: flex; align-items: center; gap: 3px; cursor: pointer;" } });
const checkbox = label.createEl("input", { type: "checkbox" });
checkbox.checked = state.activeColumns[col.id] !== undefined ? state.activeColumns[col.id] : col.defaultOn;
label.createEl("span", { text: col.name });
checkbox.addEventListener("change", (e) => { state.activeColumns[col.id] = e.target.checked; saveColumnState(); renderTable(); });
});
const topPaginationBar = container.createEl("div", { attr: { style: "display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; width: 100%; margin-top: 10px; padding: 6px 10px; background: var(--background-secondary); border-radius: 4px; border: 1px solid var(--background-modifier-border);" } });
const perPageWrapper = topPaginationBar.createEl("div", { attr: { style: "display: flex; align-items: center; gap: 6px;" }});
perPageWrapper.createEl("span", { text: "Show:", attr: { style: "font-weight: bold; color: var(--text-muted);" }});
const perPageSelect = perPageWrapper.createEl("select", { attr: { style: "padding: 2px 4px; border-radius: 4px; background: var(--background-primary); border: 1px solid var(--background-modifier-border); cursor: pointer;" }});
[10, 20, 50, 100].forEach(val => { const opt = perPageSelect.createEl("option", { value: val, text: `${val} per page` }); if (val === state.itemsPerPage) opt.selected = true; });
perPageSelect.addEventListener("change", (e) => { state.itemsPerPage = parseInt(e.target.value); state.currentPage = 1; savePrefs(); renderTable(); });
const topPageControls = topPaginationBar.createEl("div", { attr: { style: "display: flex; align-items: center; gap: 15px;" }});
const countDisplay = topPaginationBar.createEl("div", { attr: { style: "text-align: right; font-size: 0.9em; color: var(--text-muted); font-weight: bold;" } });
const tableContainer = container.createEl("div", { attr: { style: "width: 100%; overflow-x: auto; margin-top: 5px;" } });
const bottomPaginationContainer = container.createEl("div", { attr: { style: "display: flex; justify-content: center; align-items: center; gap: 15px; margin-top: 10px; margin-bottom: 20px;" }});
// --- ULTRA-LEAN RAM CACHE ---
let movieCache = [];
const loadingIndicator = tableContainer.createEl("div", { text: "β³ Processing 23,000+ Movies into RAM (Preventing Crashes)...", attr: { style: "padding: 20px; font-weight: bold; color: var(--interactive-accent);" } });
setTimeout(() => {
const rawArray = allMovies.array();
// We physically disconnect from Dataview's heavy proxy to stop memory leaks
movieCache = rawArray.map(m => {
return {
path: m.file?.path || "",
poster: m.poster || "",
title: m.title || "Untitled",
original_title: m.original_title || "",
tmdb_url: m.tmdb_url || "",
imdb_id: m.imdb_id || "",
my_rating: m.my_rating || 0,
my_review: m.my_review || "",
year: parseInt(m.year) || 0,
runtime: m.runtime || 0,
budget: m.budget || 0,
revenue: m.revenue || 0,
rating: m.rating || 0,
tagline: m.tagline || "",
genres: toArray(m.genres),
actors: toArray(m.actors),
director: toArray(m.director),
spoken_languages: toArray(m.spoken_languages),
// Special properties
owned: m.owned === true || m.owned === "true",
fav: m.fav === true || m.fav === "true",
masterpiece: m.masterpiece === true || m.masterpiece === "true",
mindfuck: m.mindfuck === true || m.mindfuck === "true",
guilty: m.guilty === true || m.guilty === "true",
hot: m.hot === true || m.hot === "true",
extreme: m.extreme === true || m.extreme === "true",
legendary: m.legendary === true || m.legendary === "true",
// Pre-computed lowercase strings for lightning-fast search
titleLower: ((m.title || "") + " " + (m.original_title || "")).toLowerCase(),
actorsLower: toArray(m.actors).map(cleanName).join(" ").toLowerCase(),
dirLower: toArray(m.director).map(cleanName).join(" ").toLowerCase(),
genreLower: toArray(m.genres).map(cleanName).join(" ").toLowerCase(),
langLower: toArray(m.spoken_languages).map(cleanName).join(" ").toLowerCase(),
langsArrayLower: toArray(m.spoken_languages).map(l => String(l).toLowerCase()),
searchBlob: [m.title, m.original_title, m.year, toArray(m.actors).map(cleanName).join(" "), toArray(m.director).map(cleanName).join(" "), toArray(m.genres).map(cleanName).join(" "), toArray(m.country).join(" "), toArray(m.spoken_languages).join(" ")].join(" ").toLowerCase()
};
});
renderTable();
}, 200);
// 4. Render Function
function renderTable() {
tableContainer.empty();
topPageControls.empty();
bottomPaginationContainer.empty();
const activeLangTarget = state.activeLang ? langBadgeData.find(b => b.label === state.activeLang) : null;
let filteredMovies = movieCache.filter(cm => {
if (state.searchAll) {
const terms = String(state.searchAll).toLowerCase().split(" ").filter(t => t);
if (!terms.every(term => cm.searchBlob.includes(term))) return false;
}
if (state.searchTitle && !cm.titleLower.includes(state.searchTitle.toLowerCase())) return false;
if (state.searchActor && !cm.actorsLower.includes(state.searchActor.toLowerCase())) return false;
if (state.searchDir && !cm.dirLower.includes(state.searchDir.toLowerCase())) return false;
if (state.searchGenre && !cm.genreLower.includes(state.searchGenre.toLowerCase())) return false;
if (state.searchLang && !cm.langLower.includes(state.searchLang.toLowerCase())) return false;
if (state.searchYear) {
const yQuery = String(state.searchYear).trim().toLowerCase();
const mYear = cm.year;
if (!isNaN(mYear)) {
if (yQuery.includes("-")) {
const parts = yQuery.split("-");
let minStr = parts[0].trim(), maxStr = parts[1].trim();
let min = parseInt(minStr);
if (!isNaN(min) && minStr.length <= 2) min = min <= 30 ? 2000 + min : 1900 + min;
let max = parseInt(maxStr);
if (!isNaN(max) && maxStr.length <= 2) {
if (!isNaN(min)) {
let century = Math.floor(min / 100) * 100; max = century + max;
if (max < min) max += 100;
} else { max = max <= 30 ? 2000 + max : 1900 + max; }
}
if (!isNaN(min) && mYear < min) return false;
if (!isNaN(max) && mYear > max) return false;
} else if (yQuery.endsWith("+")) {
let minStr = yQuery.replace("+", "");
let min = parseInt(minStr);
if (!isNaN(min)) {
if (minStr.length <= 2) min = min <= 30 ? 2000 + min : 1900 + min;
if (mYear < min) return false;
}
} else if (yQuery.endsWith("s")) {
let decStr = yQuery.replace("s", "");
let dec = parseInt(decStr);
if (!isNaN(dec)) {
if (decStr.length <= 2) dec = dec <= 30 ? 2000 + dec : 1900 + dec;
let decStart = Math.floor(dec / 10) * 10;
if (mYear < decStart || mYear >= decStart + 10) return false;
}
} else {
let exactStr = yQuery.replace(/[^0-9]/g, '');
let exact = parseInt(exactStr);
if (!isNaN(exact)) {
let target = exact;
if (exactStr.length <= 2) target = exact <= 30 ? 2000 + exact : 1900 + exact;
if (mYear !== target) return false;
} else return false;
}
} else return false;
}
if (activeLangTarget) {
if (cm.langsArrayLower.length === 0) return false;
if (state.activeLang === "EN") {
const hasEnglish = cm.langsArrayLower.some(ml => activeLangTarget.matchNames.some(tn => ml.includes(tn)));
if (!hasEnglish) return false;
} else {
const strictlyThisLang = cm.langsArrayLower.every(ml => activeLangTarget.matchNames.some(tn => ml.includes(tn)));
if (!strictlyThisLang) return false;
}
}
if (state.activeSpecial) {
if (state.activeSpecial === "foreign") {
if (cm.langsArrayLower.length === 0 || cm.langsArrayLower.some(l => l.includes("english"))) return false;
} else if (state.activeSpecial === "reviewed") {
if (!cm.my_review) return false;
} else {
if (cm[state.activeSpecial] !== true) return false;
}
}
if (state.activeGenre && !cm.genreLower.includes(state.activeGenre.toLowerCase())) return false;
if (state.activeFavActor && !cm.actorsLower.includes(state.activeFavActor.toLowerCase())) return false;
if (state.activeFavDir && !cm.dirLower.includes(state.activeFavDir.toLowerCase())) return false;
const r = cm.rating;
let bucketId = "unrated";
if (!r || r === 0) bucketId = "unrated";
else if (r < 5) bucketId = "under5";
else if (r >= 5 && r < 6) bucketId = "5";
else if (r >= 6 && r < 7) bucketId = "6";
else if (r >= 7 && r < 8) bucketId = "7";
else if (r >= 8 && r < 9) bucketId = "8";
else if (r >= 9) bucketId = "9";
if (state.activeRatings[bucketId] === false) return false;
return true;
});
filteredMovies.sort((a, b) => {
if (state.sortBy === "title") return state.sortDesc ? b.titleLower.localeCompare(a.titleLower) : a.titleLower.localeCompare(b.titleLower);
else if (state.sortBy === "year") return state.sortDesc ? b.year - a.year : a.year - b.year;
else if (state.sortBy === "rating") return state.sortDesc ? b.rating - a.rating : a.rating - b.rating;
else if (state.sortBy === "my_rating") return state.sortDesc ? b.my_rating - a.my_rating : a.my_rating - b.my_rating;
else if (state.sortBy === "runtime") return state.sortDesc ? b.runtime - a.runtime : a.runtime - b.runtime;
else if (state.sortBy === "budget") return state.sortDesc ? b.budget - a.budget : a.budget - b.budget;
else if (state.sortBy === "revenue") return state.sortDesc ? b.revenue - a.revenue : a.revenue - b.revenue;
return 0;
});
const totalItems = filteredMovies.length;
if (totalItems === 0) {
countDisplay.innerHTML = "0 movies";
tableContainer.createEl("div", { text: "No matching movies found.", attr: { style: "padding: 15px; text-align: left; color: var(--text-muted);" } });
return;
}
const totalPages = Math.ceil(totalItems / state.itemsPerPage) || 1;
if (state.currentPage > totalPages) state.currentPage = totalPages;
const startIndex = (state.currentPage - 1) * state.itemsPerPage;
const endIndex = Math.min(startIndex + state.itemsPerPage, totalItems);
const paginatedMovies = filteredMovies.slice(startIndex, endIndex);
countDisplay.innerHTML = `Showing ${startIndex + 1}-${endIndex} of <b>${totalItems}</b>`;
const activeConfigs = columnConfigs.filter(col => state.activeColumns[col.id] !== undefined ? state.activeColumns[col.id] : col.defaultOn);
const table = tableContainer.createEl("table", {
cls: "dataview table-view-table",
attr: { style: "width: 100%; min-width: 1000px; table-layout: auto; font-size: 0.95em; text-align: left; border-collapse: collapse;" }
});
const thead = table.createEl("thead");
const trHead = thead.createEl("tr");
activeConfigs.forEach(col => {
trHead.createEl("th", { text: col.name, attr: { style: "white-space: nowrap; padding: 10px; min-width: fit-content;" } });
});
const tbody = table.createEl("tbody");
paginatedMovies.forEach((cm, index) => {
const tr = tbody.createEl("tr");
if (index % 2 !== 0) tr.style.backgroundColor = "var(--background-secondary-alt)";
activeConfigs.forEach(col => {
const td = tr.createEl("td", { attr: { style: "vertical-align: top; padding: 8px; white-space: normal;" } });
if (col.id === "my_rating" || col.id === "revenue" || col.id === "year") td.style.whiteSpace = "nowrap";
td.innerHTML = col.render(cm);
});
});
function buildPaginationUI(containerElement) {
if (totalPages <= 1) return;
const prevBtn = containerElement.createEl("button", { text: "β Prev", attr: { style: "cursor: pointer; padding: 4px 12px; border-radius: 4px; background: var(--background-primary); border: 1px solid var(--background-modifier-border);" }});
if (state.currentPage === 1) prevBtn.style.opacity = "0.5";
containerElement.createEl("span", { text: `Page ${state.currentPage} of ${totalPages}`, attr: { style: "font-weight: bold; color: var(--text-normal);" }});
const nextBtn = containerElement.createEl("button", { text: "Next βΆ", attr: { style: "cursor: pointer; padding: 4px 12px; border-radius: 4px; background: var(--background-primary); border: 1px solid var(--background-modifier-border);" }});
if (state.currentPage === totalPages) nextBtn.style.opacity = "0.5";
prevBtn.addEventListener("click", () => { if (state.currentPage > 1) { state.currentPage--; savePrefs(); renderTable(); } });
nextBtn.addEventListener("click", () => { if (state.currentPage < totalPages) { state.currentPage++; savePrefs(); renderTable(); } });
}
buildPaginationUI(topPageControls);
buildPaginationUI(bottomPaginationContainer);
}
To add your own ratings, notes, and badges, edit the YAML Properties at the very top of any movie's Markdown file (between the --- lines).
1. Your Rating & Notes
- Rating: Type
my_rating: 9to highlight your personal score in the β column. - Review: Type
my_review: "Your thoughts here"to display a stylish quote block and make the movie filterable via the π button.
Example:
id: film/the-matrix_1999
title: The Matrix
year: 1999
my_rating: 9
my_review: "A cyberpunk masterpiece that completely changed cinema."
fav: truefav(β€οΈ Favorite)masterpiece(π Masterpiece)mindfuck(π§ Mind-Bending)guilty(π Guilty Pleasure)hot(πΆοΈ Hot)extreme(β’οΈ Controversial / Extreme)legendary(πΏ Legendary Film)