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/1ff356c35daf81a8a881573a70b82d4d to your computer and use it in GitHub Desktop.

Select an option

Save rickd-uk/1ff356c35daf81a8a881573a70b82d4d to your computer and use it in GitHub Desktop.
CR8R Obsidian Script.md
// 1. Upgraded to "v23" - Added Real-Time Results Counter
const stateKey = "dv_search_state_v23_" + dv.current().file.name;

const defaultState = { 
    term: "", 
    cat: true, chan: true, searchUsp: true, searchTags: true, 
    rUnder3: false, r3: false, r35: false, r4: false, r45: false, r5: true, rNone: false, 
    requireUsp: false, sourceType: "youtube",
    cCategory: true, cThumb: true, cIcon: true, cUsp: true, cPlaylists: true, cSubs: true, cRating: true,
    sFire: true, sRocket: true, sCompass: true, sBrain: true, sSexy: true, sGem: true, sKing: true,
    sOfficial: true, sLegendary: true, sWizard: true, sUni: true, sHorror: true, sRelax: true, sWow: true,
    sNone: true, 
    activeMin: 0, activeMax: 5,
    quickTag: "", 
    advancedOpen: false
};

let state;
try {
    const saved = localStorage.getItem(stateKey);
    state = saved ? { ...defaultState, ...JSON.parse(saved) } : defaultState;
} catch (e) {
    state = defaultState;
}

function saveState() {
    localStorage.setItem(stateKey, JSON.stringify(state));
}

function renderTable() {
    dv.container.innerHTML = "";
    const uiContainer = dv.el("div", "", { attr: { style: "margin-bottom: 10px; display: flex; flex-direction: column; gap: 6px;" } });
    
    // --- ROW 1: SEARCH, SOURCE SELECT & COUNTER ---
    const topRow = document.createElement("div");
    topRow.style.display = "flex";
    topRow.style.gap = "10px";
    topRow.style.alignItems = "center";

    const searchWrapper = document.createElement("div");
    searchWrapper.style.display = "flex";
    searchWrapper.style.width = "40%";
    searchWrapper.style.gap = "4px";

    const clearBtn = document.createElement("button");
    clearBtn.innerText = "✖";
    clearBtn.addEventListener("click", () => { state.term = ""; saveState(); renderTable(); });

    const searchBox = document.createElement("input");
    searchBox.type = "text";
    searchBox.placeholder = "Search...";
    searchBox.value = state.term;
    searchBox.style.width = "100%";
    searchBox.style.padding = "4px 8px";

    searchWrapper.appendChild(clearBtn);
    searchWrapper.appendChild(searchBox);
    topRow.appendChild(searchWrapper);

    const allSources = new Set(dv.pages('""').where(p => p.source_type).map(p => p.source_type));
    const uniqueSources = ["All", ...Array.from(allSources).flat().filter(Boolean)].sort();
    const sourceSelect = document.createElement("select");
    uniqueSources.forEach(type => {
        const opt = document.createElement("option");
        opt.value = type; opt.innerText = type.charAt(0).toUpperCase() + type.slice(1);
        if (state.sourceType.toLowerCase() === type.toLowerCase()) opt.selected = true;
        sourceSelect.appendChild(opt);
    });
    sourceSelect.addEventListener("change", (e) => { state.sourceType = e.target.value; saveState(); renderTable(); });
    topRow.appendChild(sourceSelect);

    // NEW: Placeholder for the results counter (pushed to the right)
    const countDisplay = document.createElement("div");
    countDisplay.style.marginLeft = "auto";
    countDisplay.style.fontSize = "0.95em";
    countDisplay.style.color = "var(--text-muted)";
    topRow.appendChild(countDisplay);

    uiContainer.appendChild(topRow);

    // --- ROW 1.5: QUICK TAGS ---
    const tagsRow = document.createElement("div");
    tagsRow.style.display = "flex";
    tagsRow.style.gap = "6px";
    tagsRow.style.alignItems = "center";
    tagsRow.style.flexWrap = "wrap";
    tagsRow.style.fontSize = "0.85em";

    const tagsLabel = document.createElement("strong");
    tagsLabel.innerText = "Quick Tags:";
    tagsRow.appendChild(tagsLabel);

    const myQuickTags = ["ai", "python", "gaming", "history", "music", "dev", "jam_tracks"]; 

    myQuickTags.forEach(tag => {
        const btn = document.createElement("button");
        btn.innerText = "#" + tag;
        btn.style.padding = "2px 8px";
        btn.style.borderRadius = "12px";
        btn.style.border = "1px solid var(--background-modifier-border)";
        btn.style.cursor = "pointer";
        
        if (state.quickTag === tag) {
            btn.style.backgroundColor = "var(--text-accent)";
            btn.style.color = "var(--background-primary)";
            btn.style.borderColor = "var(--text-accent)";
        } else {
            btn.style.backgroundColor = "var(--background-secondary)";
            btn.style.color = "var(--text-normal)";
        }

        btn.addEventListener("click", () => {
            state.quickTag = (state.quickTag === tag) ? "" : tag;
            saveState(); renderTable();
        });
        
        tagsRow.appendChild(btn);
    });

    uiContainer.appendChild(tagsRow);

    // --- ROW 2: FILTERS & RATINGS ---
    const filterRow = document.createElement("div");
    filterRow.style.display = "flex";
    filterRow.style.flexWrap = "wrap";
    filterRow.style.gap = "12px";
    filterRow.style.fontSize = "0.85em";
    filterRow.style.alignItems = "center";

    function createCheckbox(labelText, stateProp) {
        const label = document.createElement("label");
        label.style.display = "inline-flex";
        label.style.alignItems = "center";
        label.style.gap = "3px";
        label.style.cursor = "pointer";
        const cb = document.createElement("input");
        cb.type = "checkbox";
        cb.checked = state[stateProp];
        cb.addEventListener("change", (e) => { state[stateProp] = e.target.checked; saveState(); renderTable(); });
        label.appendChild(cb);
        label.appendChild(document.createTextNode(labelText));
        return label;
    }

    // Ratings + Toggle
    const ratWrap = document.createElement("div");
    ratWrap.style.display = "flex";
    ratWrap.style.gap = "6px";
    ratWrap.style.alignItems = "center";
    ratWrap.innerHTML = "<strong>⭐</strong>";
    const tglRat = document.createElement("button");
    tglRat.innerText = "±";
    tglRat.style.padding = "0 4px";
    tglRat.addEventListener("click", () => {
        const keys = ["rUnder3", "r3", "r35", "r4", "r45", "r5", "rNone"];
        const anyFalse = keys.some(k => !state[k]);
        keys.forEach(k => state[k] = anyFalse);
        saveState(); renderTable();
    });
    ratWrap.appendChild(tglRat);
    ["rUnder3", "r3", "r35", "r4", "r45", "r5", "rNone"].forEach((k, i) => {
        const labels = ["<3", "3", "3.5", "4", "4.5", "5", "?"];
        ratWrap.appendChild(createCheckbox(labels[i], k));
    });
    filterRow.appendChild(ratWrap);

    // Activity Number Inputs
    const actWrap = document.createElement("div");
    actWrap.style.display = "flex";
    actWrap.style.gap = "6px";
    actWrap.style.alignItems = "center";
    actWrap.innerHTML = "<strong>Act:</strong>";
    
    const createNumberInput = (prop, label) => {
        const wrap = document.createElement("div");
        wrap.style.display = "flex";
        wrap.style.alignItems = "center";
        wrap.style.gap = "3px";
        
        const sp = document.createElement("span");
        sp.innerText = label;
        
        const s = document.createElement("input");
        s.type = "number"; s.min = "0"; s.max = "5"; s.step = "0.5";
        s.value = state[prop]; 
        s.style.width = "45px";
        s.style.padding = "2px 4px";
        
        s.addEventListener("change", (e) => {
            let val = parseFloat(e.target.value);
            if (isNaN(val)) val = prop === "activeMin" ? 0 : 5;
            val = Math.max(0, Math.min(5, val)); 
            
            state[prop] = val;
            if (prop === "activeMin" && state.activeMin > state.activeMax) state.activeMax = state.activeMin;
            if (prop === "activeMax" && state.activeMax < state.activeMin) state.activeMin = state.activeMax;
            
            saveState(); renderTable();
        });
        wrap.appendChild(sp);
        wrap.appendChild(s);
        return wrap;
    };
    
    actWrap.appendChild(createNumberInput("activeMin", "Min:"));
    actWrap.appendChild(createNumberInput("activeMax", "Max:"));
    filterRow.appendChild(actWrap);

    uiContainer.appendChild(filterRow);

    // --- ROW 3: COLLAPSIBLE EXTRA SETTINGS ---
    const details = document.createElement("details");
    details.style.fontSize = "0.8em";
    details.style.color = "var(--text-muted)";
    details.open = state.advancedOpen; 
    
    details.addEventListener("toggle", () => {
        state.advancedOpen = details.open;
        saveState();
    });

    const summary = document.createElement("summary");
    summary.innerText = "Advanced Filters & Columns";
    summary.style.cursor = "pointer";
    details.appendChild(summary);

    const detailContent = document.createElement("div");
    detailContent.style.display = "flex";
    detailContent.style.flexDirection = "column";
    detailContent.style.gap = "8px";
    detailContent.style.padding = "8px 0";

    // Specials Row & Toggle
    const specRow = document.createElement("div");
    specRow.style.display = "flex";
    specRow.style.flexWrap = "wrap";
    specRow.style.gap = "8px";
    specRow.style.alignItems = "center";
    
    const tglSpec = document.createElement("button");
    tglSpec.innerText = "± Icons";
    tglSpec.style.padding = "0 4px";
    tglSpec.addEventListener("click", () => {
        const specKeys = ["sKing", "sHorror", "sFire", "sGem", "sBrain", "sCompass", "sRocket", "sSexy", "sOfficial", "sLegendary", "sWizard", "sUni", "sRelax", "sWow", "sNone"];
        const anyFalse = specKeys.some(k => !state[k]);
        specKeys.forEach(k => state[k] = anyFalse);
        saveState(); renderTable();
    });
    specRow.appendChild(tglSpec);

    const emojis = { sKing:"👑", sHorror:"💀", sFire:"🔥", sGem:"💎", sBrain:"🧠", sCompass:"🧭", sRocket:"🚀", sSexy:"🌶️", sOfficial:"🏛️", sLegendary:"🗿", sWizard:"🧙‍♂️", sUni:"🎓", sRelax:"🪶", sWow:"💫", sNone:"∅" };
    Object.keys(emojis).forEach(k => specRow.appendChild(createCheckbox(emojis[k], k)));
    detailContent.appendChild(specRow);

    // Search Fields 
    const fieldsMisc = document.createElement("div");
    fieldsMisc.style.display = "flex";
    fieldsMisc.style.gap = "15px";
    fieldsMisc.appendChild(createCheckbox("Must USP", "requireUsp"));
    const searchMap = [ {l: "Cat", p: "cat"}, {l: "Chan", p: "chan"}, {l: "Search USP", p: "searchUsp"}, {l: "Tags", p: "searchTags"} ];
    searchMap.forEach(c => fieldsMisc.appendChild(createCheckbox(c.l, c.p)));
    detailContent.appendChild(fieldsMisc);

    // Columns & Toggle
    const colsMisc = document.createElement("div");
    colsMisc.style.display = "flex";
    colsMisc.style.gap = "12px";
    colsMisc.style.alignItems = "center";
    
    const tglCols = document.createElement("button");
    tglCols.innerText = "± Cols";
    tglCols.style.padding = "0 4px";
    tglCols.addEventListener("click", () => {
        const colKeys = ["cCategory", "cThumb", "cIcon", "cUsp", "cPlaylists", "cSubs", "cRating"];
        const anyFalse = colKeys.some(k => !state[k]);
        colKeys.forEach(k => state[k] = anyFalse);
        saveState(); renderTable();
    });
    colsMisc.appendChild(tglCols);

    const colMap = [
        {l: "Category", p: "cCategory"}, {l: "Img", p: "cThumb"}, {l: "📄", p: "cIcon"}, 
        {l: "USP", p: "cUsp"}, {l: "PL", p: "cPlaylists"}, {l: "Subs", p: "cSubs"}, {l: "Rating", p: "cRating"}
    ];
    colMap.forEach(c => colsMisc.appendChild(createCheckbox(c.l, c.p)));
    detailContent.appendChild(colsMisc);

    details.appendChild(detailContent);
    uiContainer.appendChild(details);

    dv.container.appendChild(uiContainer);

    // --- LOGIC ---
    searchBox.addEventListener("input", (e) => {
        state.term = e.target.value;
        saveState();
        renderTable(); 
        const ni = dv.container.querySelector("input[type='text']");
        if (ni) { ni.focus(); ni.setSelectionRange(state.term.length, state.term.length); }
    });

    let pages = dv.pages('""').where(p => (p.file.name !== "_" && (!p.file.folder || (!p.file.folder.startsWith("_") && !p.file.folder.includes("/_")))));

    pages = pages.where(p => {
        const v = p.active != null ? Number(p.active) : null;
        if (v === null) return (state.activeMin === 0 && state.activeMax === 5);
        return v >= state.activeMin && v <= state.activeMax;
    });

    if (state.sourceType !== "All") {
        pages = pages.where(p => [p.source_type].flat().some(t => t?.toLowerCase() === state.sourceType.toLowerCase()));
    }

    if (state.quickTag) {
        const qt = state.quickTag.toLowerCase();
        pages = pages.where(p => {
            let tags = p.file.etags || [];
            // Dataview sometimes returns a single string instead of an array, so let's handle both
            if (typeof tags === 'string') tags = [tags];
            return tags.some(tag => tag.toLowerCase().includes(qt));
        });
    }

    pages = pages.where(p => {
        const r = Number(p.rating);
        const isUnrated = !p.rating || isNaN(r);
        
        let ratingMatch = false;
        if (state.rNone && isUnrated) ratingMatch = true;
        if (state.rUnder3 && r > 0 && r < 3) ratingMatch = true;
        if (state.r3 && r === 3) ratingMatch = true;
        if (state.r35 && r === 3.5) ratingMatch = true;
        if (state.r4 && r === 4) ratingMatch = true;
        if (state.r45 && r === 4.5) ratingMatch = true;
        if (state.r5 && r === 5) ratingMatch = true;

        if (!ratingMatch) return false; 

        const specMap = { sKing:"king", sHorror:"horror", sFire:"fire", sGem:"gem", sBrain:"brain", sCompass:"compass", sRocket:"rocket", sSexy:"sexy", sOfficial:"official", sLegendary:"legendary", sWizard:"wizard", sUni:"uni", sRelax:"relax", sWow:"wow" };
        
        let specialMatch = false;
        let hasAnyIcon = false;
        
        for (let key in specMap) {
            if (p[specMap[key]] === true || p[specMap[key]] === "true") {
                hasAnyIcon = true;
                if (state[key]) {
                    specialMatch = true;
                    break;
                }
            }
        }

        if (!hasAnyIcon && state.sNone) {
            specialMatch = true;
        }

        return specialMatch; 
    });

    if (state.requireUsp) pages = pages.where(p => p.usp?.trim());
    if (state.term) {
        const t = state.term.toLowerCase();
        pages = pages.where(p => (state.cat && p.file.folder?.toLowerCase().includes(t)) || (state.chan && p.file.name.toLowerCase().includes(t)) || (state.searchUsp && p.usp?.toLowerCase().includes(t)) || (state.searchTags && String(p.file.etags).toLowerCase().includes(t)));
    }

    // UPDATE COUNTER DISPLAY NOW THAT FILTERING IS DONE
    countDisplay.innerHTML = `<strong>${pages.length}</strong> results`;

    // --- TABLE RENDER ---
    const formatThumb = (t) => t ? (typeof t === 'string' && t.startsWith("http") ? `<img src="${t}" style="width:40px;height:40px;object-fit:cover;border-radius:4px;">` : `![[${t.path || t.replace(/\[|\]/g, '')}|40]]`) : `<div style="width:40px;height:40px;background:var(--background-modifier-border);border-radius:4px;"></div>`;
    
    let grouped = pages.groupBy(p => p.file.folder?.split("/").pop() || "Root").sort(g => g.key, "asc");
    
    let headers = [];
    if (state.cCategory) headers.push("Category");
    if (state.cThumb) headers.push("Img");
    if (state.cIcon) headers.push("📄");
    headers.push("Channel"); 
    if (state.cUsp) headers.push("USP");
    if (state.cPlaylists) headers.push("PL");
    if (state.cSubs) headers.push("Subs");
    if (state.cRating) headers.push("Rating");

    let finalRows = [];
    grouped.forEach(g => {
        let headerRow = [];
        if (headers.length > 0) {
            headerRow.push(`<span style="font-weight:bold;color:var(--text-accent);">${g.key}</span>`);
            for(let i = 1; i < headers.length; i++) {
                headerRow.push("<hr style='opacity:0.2;margin:0;'>");
            }
            finalRows.push(headerRow);
        }

        g.rows.forEach(r => {
            let row = [];
            if (state.cCategory) row.push(""); 
            if (state.cThumb) row.push(formatThumb(r.thumbnail));
            if (state.cIcon) row.push(`[[${r.file.path}|📄]]`);
            
            row.push(r.url ? `<a href="${r.url}" style="font-weight:bold;text-decoration:none;">${r.file.name}</a>` : r.file.name);
            
            if (state.cUsp) row.push(r.usp || "");
            if (state.cPlaylists) {
                let pl = [r.playlists].flat().filter(x => x?.url).map(x => `<a href="${x.url}" target="_blank" style="font-size:0.8em;">▶️ ${x.name}</a>`);
                row.push(`<div style="display:flex;flex-direction:column;">${pl.join("")}</div>`);
            }
            if (state.cSubs) row.push(r.subs ? (isNaN(r.subs) ? r.subs : Number(r.subs).toLocaleString()) : "");
            
            if (state.cRating) {
                const icons = { fire:"🔥", rocket:"🚀", compass:"🧭", brain:"🧠", sexy:"🌶️", gem:"💎", king:"👑", horror:"💀", official:"🏛️", legendary:"🗿", wizard:"🧙‍♂️", uni:"🎓", relax:"🪶", wow:"💫" };
                let out = "";
                
                for (let k in icons) {
                    if (r[k] === true || r[k] === "true") { 
                        out = `<div style="font-size:1.5em; text-align:center;">${icons[k]}</div>`; 
                        break; 
                    }
                }
                
                if (!out && r.rating) {
                    const stars = { 5: ["#FFD700", "★★★★★"], 4.5: ["#00E5FF", "★★★★½"], 4: ["#00E5FF", "★★★★"], 3.5: ["#FF9800", "★★★½"], 3: ["#FF9800", "★★★"] };
                    if (stars[r.rating]) {
                        out = `<div style="text-align:center; color:${stars[r.rating][0]}">${stars[r.rating][1]}</div>`;
                    } else {
                        out = `<div style="text-align:center;">${r.rating}</div>`;
                    }
                }
                row.push(out);
            }
            finalRows.push(row);
        });
    });

    dv.table(headers, finalRows);
}

renderTable();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment