Skip to content

Instantly share code, notes, and snippets.

@rickd-uk
Last active March 12, 2026 00:45
Show Gist options
  • Select an option

  • Save rickd-uk/245cda7538ae49972a5c1378d7fd3d58 to your computer and use it in GitHub Desktop.

Select an option

Save rickd-uk/245cda7538ae49972a5c1378d7fd3d58 to your computer and use it in GitHub Desktop.
Obsidian - Movies Search & Table
// -----------------------------
// 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);
}

🎬 Dashboard Cheat Sheet: Personalizing Your Database

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: 9 to 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: true
  • fav (❀️ Favorite)
  • masterpiece (πŸ‘‘ Masterpiece)
  • mindfuck (🧠 Mind-Bending)
  • guilty (πŸ™ˆ Guilty Pleasure)
  • hot (🌢️ Hot)
  • extreme (☒️ Controversial / Extreme)
  • legendary (πŸ—Ώ Legendary Film)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment