From 3baf3d2545ba6ece6b74a64c0def59bd0774cf54 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Wed, 10 Jun 2026 16:34:12 +0000
Subject: [PATCH] =Laid the groundwork for an improved DashboardManager.php setup. Have to put it aside so I can get the dang Northeh done though.
---
assets/js/concise/DataStore.js | 186 +++++++++++++++++++++++++++++-----------------
1 files changed, 117 insertions(+), 69 deletions(-)
diff --git a/assets/js/concise/DataStore.js b/assets/js/concise/DataStore.js
index 58ec874..539c82b 100644
--- a/assets/js/concise/DataStore.js
+++ b/assets/js/concise/DataStore.js
@@ -45,13 +45,12 @@
* @param {object|array} configs An object defining the store, or an array of objects defining the stores
* @param {number} version the database version
*/
- register(name, configs = [], version = 1.2) {
+ register(name, configs = [], version = 1.25) {
if (!Array.isArray(configs)) configs = [configs];
if (configs.length === 0) return;
-
if (!this.dbConfig.has(name)) {
this.dbConfig.set(name, {
- dbName: `jvb_${name}`,
+ dbName: `${jvbBase.base}${name}`,
version: version,
stores: {},
_initialized: false
@@ -85,6 +84,8 @@
ignore: [], //any filters to ignore when filtering store locally
required: null,
+ isAuth: false,
+
// Cache
TTL: 3600000, // 1 hour
useHttpCaching: true,
@@ -108,6 +109,7 @@
store.ignoreFilters = new Set([
... ['search', 'page', 'per_page', 'orderby', 'order'],
+ ... ['context', 'source'],
... store.config.ignore
]);
@@ -124,6 +126,7 @@
}
});
+
// Initialize database asynchronously
this.initDB(name).catch(error => {
console.error(`Failed to initialize store "${name}":`, error);
@@ -517,9 +520,10 @@
const cached = store.cache.get(cacheKey);
if (cached && this.isCacheValid(cached, store.config.TTL)) {
+ let items = cached.items.map(itemId => this.get(name, itemId));
this.notify(name, 'data-loaded', {
cached: true,
- items: cached.items || []
+ items: items??[]
});
return cached;
}
@@ -540,11 +544,27 @@
const controller = new AbortController();
store.currentRequest = controller;
- const response = await fetch(url, {
- method: 'GET',
- headers,
- signal: controller.signal
- });
+ let response;
+ if (store.isAuth) {
+ response = await window.auth.fetch(url, {
+ method: 'GET',
+ headers,
+ signal: controller.signal
+ });
+ } else {
+ response = await fetch(url, {
+ method: 'GET',
+ headers,
+ signal: controller.signal
+ });
+ }
+
+ if (!response.ok) {
+ // Access the error details from the response body
+ const errorBody = await response.text();
+ // Throw a new error with a descriptive message
+ throw new Error(`HTTP error! status: ${response.status}, message: ${errorBody}`);
+ }
if (response.status === 304) {
// 304 means "Not Modified" - use cached data if available
@@ -579,7 +599,6 @@
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
-
const data = await response.json();
await this.processFetchedData(name, data, cacheKey, response);
@@ -595,7 +614,8 @@
const isAbortError = error?.name === 'AbortError';
if (!isAbortError) {
- console.error(`Fetch error for store "${name}":`, error);
+ console.error(`Fetch error for store "${name}":`, error.message);
+ console.dir(error);
this.notify(name, 'fetch-error', { error });
throw error;
}
@@ -901,6 +921,7 @@
if (type === 'object') {
const processed = {};
for (const [key, value] of Object.entries(obj)) {
+ if (value === undefined) continue;
const result = this.processForStorage(value, validate, `${path}.${key}`);
if (!result.valid) return result;
// Include null values, skip undefined
@@ -1074,42 +1095,62 @@
// First check if we have cached results for exact filters
if (cacheEntry?.items) {
- return this.applyOrdering(
- cacheEntry.items.reduce((acc, id) => {
- const item = store.data.get(id);
- if (item) acc.push(item);
- return acc;
- }, []),
- store
- );
+ const items = cacheEntry.items.reduce((acc, id) => {
+ const item = store.data.get(id);
+ if (item) acc.push(item);
+ return acc;
+ }, []);
+ return this.applyOrdering(items, store);
}
const allItems = Array.from(store.data.values());
+
const searchQuery = store.filters.search?.toLowerCase().trim() || '';
const filterPredicates = [];
+
+ // Handle taxonomy filters separately
+ if (store.filters.taxonomy && typeof store.filters.taxonomy === 'object') {
+ Object.entries(store.filters.taxonomy).forEach(([taxonomy, termIds]) => {
+ const acceptedTermIds = Array.isArray(termIds) ? termIds : [termIds];
+
+ filterPredicates.push(item => {
+ if (!item.taxonomies || !item.taxonomies[taxonomy]) {
+ return false;
+ }
+ const itemTermIds = Object.keys(item.taxonomies[taxonomy]).map(id => parseInt(id));
+ const matches = acceptedTermIds.some(termId => itemTermIds.includes(parseInt(termId)));
+ return matches;
+ });
+ });
+ }
+
+ // Handle other filters
for (const [key, value] of Object.entries(store.filters)) {
- if (store.ignoreFilters.has(key)) continue;
+ if (key === 'taxonomy') {
+ if (typeof value === 'string' && !value.includes(',')) {
+ filterPredicates.push(item => item.taxonomy === value);
+ }
+ continue;
+ }
+ if (store.ignoreFilters.has(key)) {
+ continue;
+ }
if (value === null || value === undefined || value === '') continue;
if (value === 'all') continue;
- // Comma-separated values
if (typeof value === 'string' && value.includes(',')) {
const accepted = value.split(',').map(v => v.trim());
filterPredicates.push(item => accepted.includes(String(item[key])));
- continue;
+ } else {
+ filterPredicates.push(item => String(item[key]) === String(value));
}
-
- filterPredicates.push(item => String(item[key]) === String(value));
}
const filtered = allItems.filter(item => {
- // Apply all non-search filters
for (const predicate of filterPredicates) {
if (!predicate(item)) return false;
}
-
- // Apply search if present
return !(searchQuery && !this.searchObject(item, searchQuery));
});
@@ -1120,37 +1161,50 @@
if (!Array.isArray(items)) items = Array.from(items);
if (items.length === 0) return items;
- if (store.filters.orderby || store.filters.order) {
- const orderby = store.filters.orderby || 'date';
- const order = (store.filters.order || 'desc').toLowerCase();
+ const orderby = store.filters.orderby || 'date';
+ const order = (store.filters.order || 'desc').toLowerCase();
- items.sort((a, b) => {
- let aVal, bVal;
-
- switch (orderby) {
- case 'alphabetical':
- case 'title':
- aVal = (a.fields?.post_title || a.title || a.name || '').toLowerCase();
- bVal = (b.fields?.post_title || b.title || b.name || '').toLowerCase();
- break;
- case 'modified':
- aVal = new Date(a.modified || 0);
- bVal = new Date(b.modified || 0);
- break;
- case 'date':
- default:
- aVal = new Date(a.date || 0);
- bVal = new Date(b.date || 0);
- }
-
- if (aVal < bVal) return order === 'asc' ? -1 : 1;
- if (aVal > bVal) return order === 'asc' ? 1 : -1;
- return 0;
- });
+ // Handle random ordering
+ if (['random', 'rand'].includes(orderby) || ['random', 'rand'].includes(order)) {
+ return this.shuffle(items);
}
+
+ items.sort((a, b) => {
+ let aVal, bVal;
+
+ switch (orderby) {
+ case 'alphabetical':
+ case 'title':
+ aVal = (a.title || a.name || '').toLowerCase();
+ bVal = (b.title || b.name || '').toLowerCase();
+ break;
+ case 'modified':
+ aVal = new Date(a.modified || a.date || 0);
+ bVal = new Date(b.modified || b.date || 0);
+ break;
+ case 'date':
+ default:
+ aVal = new Date(a.date || a.modified || 0);
+ bVal = new Date(b.date || b.modified || 0);
+ }
+
+ if (aVal < bVal) return order === 'asc' ? -1 : 1;
+ if (aVal > bVal) return order === 'asc' ? 1 : -1;
+ return 0;
+ });
+
return items;
}
+ shuffle(items) {
+ const array = items.slice();
+ for (let i = array.length - 1; i > 0; i--) {
+ const j = Math.floor(Math.random() * (i + 1));
+ [array[i], array[j]] = [array[j], array[i]];
+ }
+ return array;
+ }
+
searchObject(obj, search) {
if (!obj || typeof obj !== 'object') {
return typeof obj === 'string' && obj.toLowerCase().includes(search);
@@ -1201,23 +1255,22 @@
store.filters[key] = value;
}
});
-
this.notify(name, 'filters-changed', {
oldFilters,
filters: store.filters,
updates
});
- this.notify(name, 'data-loaded', {
- cached: true,
- items: this.getFiltered(name)
- });
-
const shouldFetch = await this.shouldFetchWithFilters(name, updates, oldFilters);
+
if (store.config.endpoint && shouldFetch) {
await this.fetch(name);
- } else if (store.config.endpoint) {
- this.notify(name, 'data-loaded');
+ } else {
+ const filtered = this.getFiltered(name);
+ this.notify(name, 'data-loaded', {
+ cached: true,
+ items: filtered
+ });
}
}
@@ -1237,14 +1290,9 @@
}
if (store.lastResponse.has_more === false) {
- // Check if new filters are a subset of what we have
- const isSubsetFilter = Object.entries(updates).every(([key, value]) => {
- if (store.ignoreFilters.has(key)) return true;
- if (key === 'page') return true; // Handle pagination locally
- return true; // We have all data, can filter locally
- });
-
- if (isSubsetFilter) return false;
+ if (this.hasCompleteData(store, store.filters)) {
+ return false;
+ }
}
if ('page' in updates) {
--
Gitblit v1.10.0