wekan/client/lib/cardSearch.js
2025-10-21 15:08:50 +03:00

355 lines
12 KiB
JavaScript

import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
import Cards from '../../models/cards';
import SessionData from '../../models/usersessiondata';
import {QueryDebug} from "../../config/query-classes";
import {OPERATOR_DEBUG} from "../../config/search-const";
export class CardSearchPagedComponent extends BlazeComponent {
onCreated() {
this.searching = new ReactiveVar(false);
this.hasResults = new ReactiveVar(false);
this.hasQueryErrors = new ReactiveVar(false);
this.query = new ReactiveVar('');
this.resultsHeading = new ReactiveVar('');
this.searchLink = new ReactiveVar(null);
this.results = new ReactiveVar([]);
this.hasNextPage = new ReactiveVar(false);
this.hasPreviousPage = new ReactiveVar(false);
this.resultsCount = 0;
this.totalHits = 0;
this.queryErrors = null;
this.resultsPerPage = 25;
this.sessionId = SessionData.getSessionId();
this.subscriptionHandle = null;
this.serverError = new ReactiveVar(false);
this.sessionData = null;
this.debug = new ReactiveVar(new QueryDebug());
const that = this;
this.subscriptionCallbacks = {
onReady() {
if (process.env.DEBUG === 'true') {
console.log('Subscription ready, getting results...');
console.log('Subscription ready - sessionId:', that.sessionId);
}
// Wait for session data to be available (with timeout)
let waitCount = 0;
const maxWaitCount = 50; // 10 seconds max wait
const waitForSessionData = () => {
waitCount++;
const sessionData = that.getSessionData();
if (process.env.DEBUG === 'true') {
console.log('waitForSessionData - attempt', waitCount, 'session data:', sessionData);
}
if (sessionData) {
const results = that.getResults();
if (process.env.DEBUG === 'true') {
console.log('Search results count:', results ? results.length : 0);
}
// If no results and this is a due cards search, try to retry
if ((!results || results.length === 0) && that.searchRetryCount !== undefined && that.searchRetryCount < that.maxRetries) {
if (process.env.DEBUG === 'true') {
console.log('No results found, retrying search...');
}
that.searchRetryCount++;
Meteor.setTimeout(() => {
if (that.performSearch) {
that.performSearch();
}
}, 500);
return;
}
that.searching.set(false);
that.hasResults.set(true);
that.serverError.set(false);
} else if (waitCount < maxWaitCount) {
// Session data not available yet, wait a bit more
if (process.env.DEBUG === 'true') {
console.log('Session data not available yet, waiting... (attempt', waitCount, 'of', maxWaitCount, ')');
}
Meteor.setTimeout(waitForSessionData, 200);
} else {
// Timeout reached, try fallback search
if (process.env.DEBUG === 'true') {
console.log('Timeout reached waiting for session data, trying fallback search');
}
const results = that.getResults();
if (process.env.DEBUG === 'true') {
console.log('Fallback search results count:', results ? results.length : 0);
}
if (results && results.length > 0) {
that.searching.set(false);
that.hasResults.set(true);
that.serverError.set(false);
} else {
that.searching.set(false);
that.hasResults.set(false);
that.serverError.set(true);
}
}
};
// Start waiting for session data
Meteor.setTimeout(waitForSessionData, 100);
},
onError(error) {
if (process.env.DEBUG === 'true') {
console.log('Subscription error:', error);
console.log('Error.reason:', error.reason);
console.log('Error.message:', error.message);
console.log('Error.stack:', error.stack);
}
that.searching.set(false);
that.hasResults.set(false);
that.serverError.set(true);
},
};
}
resetSearch() {
this.searching.set(false);
this.results.set([]);
this.hasResults.set(false);
this.hasQueryErrors.set(false);
this.resultsHeading.set('');
this.serverError.set(false);
this.resultsCount = 0;
this.totalHits = 0;
this.queryErrors = null;
this.debug.set(new QueryDebug());
}
getSessionData(sessionId) {
const sessionIdToUse = sessionId || SessionData.getSessionId();
if (process.env.DEBUG === 'true') {
console.log('getSessionData - looking for sessionId:', sessionIdToUse);
}
// Try using the raw SessionData collection instead of ReactiveCache
const sessionData = SessionData.findOne({
sessionId: sessionIdToUse,
});
if (process.env.DEBUG === 'true') {
console.log('getSessionData - found session data (raw):', sessionData);
}
// Also try ReactiveCache for comparison
const reactiveSessionData = ReactiveCache.getSessionData({
sessionId: sessionIdToUse,
});
if (process.env.DEBUG === 'true') {
console.log('getSessionData - found session data (reactive):', reactiveSessionData);
}
return sessionData || reactiveSessionData;
}
getResults() {
// eslint-disable-next-line no-console
// console.log('getting results');
this.sessionData = this.getSessionData();
// eslint-disable-next-line no-console
if (process.env.DEBUG === 'true') {
console.log('getResults - sessionId:', this.sessionId);
console.log('getResults - session data:', this.sessionData);
}
const cards = [];
if (this.sessionData && this.sessionData.cards) {
if (process.env.DEBUG === 'true') {
console.log('getResults - cards array length:', this.sessionData.cards.length);
}
this.sessionData.cards.forEach(cardId => {
const card = ReactiveCache.getCard(cardId);
if (process.env.DEBUG === 'true') {
console.log('getResults - card:', cardId, card);
}
cards.push(card);
});
this.queryErrors = this.sessionData.errors || [];
} else {
if (process.env.DEBUG === 'true') {
console.log('getResults - no sessionData or no cards array, trying direct card search');
}
// Fallback: try to get cards directly from the client-side collection
// Use a more efficient query with limit and sort
const selector = {
type: 'cardType-card',
dueAt: { $exists: true, $nin: [null, ''] }
};
const options = {
sort: { dueAt: 1 }, // Sort by due date ascending (oldest first)
limit: 100 // Limit to 100 cards for performance
};
const allCards = Cards.find(selector, options).fetch();
if (process.env.DEBUG === 'true') {
console.log('getResults - direct card search found:', allCards ? allCards.length : 0, 'cards');
}
if (allCards && allCards.length > 0) {
allCards.forEach(card => {
if (card && card._id) {
cards.push(card);
}
});
}
this.queryErrors = [];
}
if (this.queryErrors.length) {
// console.log('queryErrors:', this.queryErrorMessages());
this.hasQueryErrors.set(true);
// return null;
}
this.debug.set(new QueryDebug(this.sessionData ? this.sessionData.debug : null));
if (process.env.DEBUG === 'true') {
console.log('debug:', this.debug.get().get());
console.log('debug.show():', this.debug.get().show());
console.log('debug.showSelector():', this.debug.get().showSelector());
}
if (cards) {
if (this.sessionData) {
this.totalHits = this.sessionData.totalHits || 0;
this.resultsCount = cards.length;
this.resultsStart = this.sessionData.lastHit - this.resultsCount + 1;
this.resultsEnd = this.sessionData.lastHit;
this.resultsHeading.set(this.getResultsHeading());
this.results.set(cards);
this.hasNextPage.set(this.sessionData.lastHit < this.sessionData.totalHits);
this.hasPreviousPage.set(
this.sessionData.lastHit - this.sessionData.resultsCount > 0,
);
} else {
this.totalHits = cards.length;
this.resultsCount = cards.length;
this.resultsStart = 1;
this.resultsEnd = cards.length;
this.resultsHeading.set(this.getResultsHeading());
this.results.set(cards);
this.hasNextPage.set(false);
this.hasPreviousPage.set(false);
}
return cards;
}
this.resultsCount = 0;
return null;
}
stopSubscription() {
if (this.subscriptionHandle) {
this.subscriptionHandle.stop();
}
}
getSubscription(queryParams) {
if (process.env.DEBUG === 'true') {
console.log('Subscribing to globalSearch with:', {
sessionId: this.sessionId,
params: queryParams.params,
text: queryParams.text
});
}
// Subscribe to both globalSearch and sessionData
const globalSearchHandle = Meteor.subscribe(
'globalSearch',
this.sessionId,
queryParams.params,
queryParams.text,
this.subscriptionCallbacks,
);
const sessionDataHandle = Meteor.subscribe('sessionData', this.sessionId);
if (process.env.DEBUG === 'true') {
console.log('Subscribed to sessionData with sessionId:', this.sessionId);
}
return globalSearchHandle;
}
runGlobalSearch(queryParams) {
this.searching.set(true);
this.debug.set(new QueryDebug());
this.stopSubscription();
this.subscriptionHandle = this.getSubscription(queryParams);
}
queryErrorMessages() {
const messages = [];
this.queryErrors.forEach(err => {
let value = err.color ? TAPi18n.__(`color-${err.value}`) : err.value;
if (!value) {
value = err.value;
}
messages.push(TAPi18n.__(err.tag, value));
});
return messages;
}
nextPage() {
this.searching.set(true);
this.stopSubscription();
this.subscriptionHandle = Meteor.subscribe(
'nextPage',
this.sessionId,
this.subscriptionCallbacks,
);
}
previousPage() {
this.searching.set(true);
this.stopSubscription();
this.subscriptionHandle = Meteor.subscribe(
'previousPage',
this.sessionId,
this.subscriptionCallbacks,
);
}
getResultsHeading() {
if (this.resultsCount === 0) {
return TAPi18n.__('no-cards-found');
} else if (this.resultsCount === 1) {
return TAPi18n.__('one-card-found');
} else if (this.resultsCount === this.totalHits) {
return TAPi18n.__('n-cards-found', {sprintf: [this.resultsCount]});
}
return TAPi18n.__('n-n-of-n-cards-found', {
start: this.resultsStart,
end: this.resultsEnd,
total: this.totalHits,
});
}
getSearchHref() {
const baseUrl = window.location.href.replace(/([?#].*$|\s*$)/, '');
return `${baseUrl}?q=${encodeURIComponent(this.query.get())}`;
}
events() {
return [
{
'click .js-next-page'(evt) {
evt.preventDefault();
this.nextPage();
},
'click .js-previous-page'(evt) {
evt.preventDefault();
this.previousPage();
},
},
];
}
}