mirror of
https://github.com/frappe/books.git
synced 2025-01-23 15:18:24 +00:00
incr: get doc search working
- temporarily disable filters
This commit is contained in:
parent
6042f70b84
commit
e008028b14
@ -78,7 +78,7 @@
|
||||
{{ si.label }}
|
||||
</p>
|
||||
<p class="text-gray-600 text-sm ml-3">
|
||||
{{ si.more.join(', ') }}
|
||||
{{ si.more.filter(Boolean).join(', ') }}
|
||||
</p>
|
||||
</div>
|
||||
<p
|
||||
@ -113,7 +113,7 @@
|
||||
<hr />
|
||||
<div class="m-1 flex justify-between flex-col gap-2 text-sm select-none">
|
||||
<!-- Group Filters -->
|
||||
<div class="flex justify-between">
|
||||
<div class="flex justify-between" v-if="false">
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
v-for="g in searchGroups"
|
||||
@ -154,11 +154,8 @@
|
||||
</Modal>
|
||||
</template>
|
||||
<script>
|
||||
import { t } from 'fyo';
|
||||
import { fuzzyMatch } from 'src/utils';
|
||||
import { getBgTextColorClass } from 'src/utils/colors';
|
||||
import { docSearch, getSearchList, searchGroups } from 'src/utils/search';
|
||||
import { routeTo } from 'src/utils/ui';
|
||||
import { getGroupLabelMap, searcher, searchGroups } from 'src/utils/search';
|
||||
import { useKeys } from 'src/utils/vueUtils';
|
||||
import { getIsNullOrUndef } from 'utils/';
|
||||
import { nextTick, watch } from 'vue';
|
||||
@ -176,7 +173,7 @@ export default {
|
||||
openModal: false,
|
||||
inputValue: '',
|
||||
searchList: [],
|
||||
docSearch: null,
|
||||
searcher: null,
|
||||
totalLength: 0,
|
||||
groupFilters: {
|
||||
List: true,
|
||||
@ -189,14 +186,13 @@ export default {
|
||||
},
|
||||
components: { Modal },
|
||||
async mounted() {
|
||||
this.docSearch = docSearch;
|
||||
await this.docSearch.fetchKeywords();
|
||||
this.searcher = searcher;
|
||||
await this.searcher.initializeKeywords();
|
||||
|
||||
if (fyo.store.isDevelopment) {
|
||||
window.search = docSearch;
|
||||
window.search = this;
|
||||
}
|
||||
|
||||
this.makeSearchList();
|
||||
watch(this.keys, (keys) => {
|
||||
if (
|
||||
keys.size === 2 &&
|
||||
@ -260,6 +256,7 @@ export default {
|
||||
},
|
||||
open() {
|
||||
this.openModal = true;
|
||||
this.searcher.updateKeywords();
|
||||
nextTick(() => {
|
||||
this.$refs.input.focus();
|
||||
});
|
||||
@ -294,17 +291,6 @@ export default {
|
||||
const ref = this.$refs.suggestions[this.idx];
|
||||
ref.scrollIntoView({ block: 'nearest' });
|
||||
},
|
||||
async makeSearchList() {
|
||||
const searchList = getSearchList();
|
||||
this.searchList = searchList.map((d) => {
|
||||
if (d.route && !d.action) {
|
||||
d.action = () => {
|
||||
routeTo(d.route);
|
||||
};
|
||||
}
|
||||
return d;
|
||||
});
|
||||
},
|
||||
getGroupFilterButtonClass(g) {
|
||||
const isOn = this.groupFilters[g];
|
||||
const color = this.groupColorMap[g];
|
||||
@ -317,13 +303,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
groupLabelMap() {
|
||||
return {
|
||||
Create: t`Create`,
|
||||
List: t`List`,
|
||||
Report: t`Report`,
|
||||
Docs: t`Docs`,
|
||||
Page: t`Page`,
|
||||
};
|
||||
return getGroupLabelMap();
|
||||
},
|
||||
groupColorMap() {
|
||||
return {
|
||||
@ -341,29 +321,10 @@ export default {
|
||||
}, {});
|
||||
},
|
||||
suggestions() {
|
||||
const filters = new Set(
|
||||
this.searchGroups.filter((g) => this.groupFilters[g])
|
||||
);
|
||||
|
||||
const nonDocs = this.searchList
|
||||
.filter((si) => filters.has(si.group))
|
||||
.map((si) => ({
|
||||
...fuzzyMatch(this.inputValue, `${si.label} ${si.group}`),
|
||||
si,
|
||||
}))
|
||||
.filter(({ isMatch }) => isMatch)
|
||||
.sort((a, b) => a.distance - b.distance)
|
||||
.map(({ si }) => si);
|
||||
|
||||
let docs = [];
|
||||
if (this.groupFilters.Docs && this.inputValue) {
|
||||
docs = this.docSearch.search(this.inputValue);
|
||||
}
|
||||
|
||||
const all = [docs, nonDocs].flat();
|
||||
const suggestions = this.searcher.search(this.inputValue);
|
||||
// eslint-disable-next-line
|
||||
this.totalLength = all.length;
|
||||
return all.slice(0, 50);
|
||||
this.totalLength = suggestions.length;
|
||||
return suggestions.slice(0, 50);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import ChartOfAccounts from 'src/pages/ChartOfAccounts.vue';
|
||||
import Dashboard from 'src/pages/Dashboard/Dashboard.vue';
|
||||
import DataImport from 'src/pages/DataImport.vue';
|
||||
@ -116,6 +117,20 @@ const routes: RouteRecordRaw[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export function getEntryRoute(schemaName: string, name: string) {
|
||||
if (
|
||||
[
|
||||
ModelNameEnum.SalesInvoice,
|
||||
ModelNameEnum.PurchaseInvoice,
|
||||
ModelNameEnum.JournalEntry,
|
||||
].includes(schemaName as ModelNameEnum)
|
||||
) {
|
||||
return `/edit/${schemaName}/${name}`;
|
||||
}
|
||||
|
||||
return `/list/${schemaName}?edit=1&schemaName=${schemaName}&name=${name}`;
|
||||
}
|
||||
|
||||
const router = createRouter({ routes, history: createWebHistory() });
|
||||
|
||||
export default router;
|
||||
|
@ -39,9 +39,9 @@ export function stringifyCircular(
|
||||
});
|
||||
}
|
||||
|
||||
export function fuzzyMatch(keyword: string, candidate: string) {
|
||||
const keywordLetters = [...keyword];
|
||||
const candidateLetters = [...candidate];
|
||||
export function fuzzyMatch(input: string, target: string) {
|
||||
const keywordLetters = [...input];
|
||||
const candidateLetters = [...target];
|
||||
|
||||
let keywordLetter = keywordLetters.shift();
|
||||
let candidateLetter = candidateLetters.shift();
|
||||
@ -63,7 +63,7 @@ export function fuzzyMatch(keyword: string, candidate: string) {
|
||||
}
|
||||
|
||||
if (keywordLetter !== undefined) {
|
||||
distance = -1;
|
||||
distance = Number.MAX_SAFE_INTEGER;
|
||||
isMatch = false;
|
||||
} else {
|
||||
distance += candidateLetters.length;
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { t } from 'fyo';
|
||||
import { DocValueMap } from 'fyo/core/types';
|
||||
import { Dictionary, groupBy } from 'lodash';
|
||||
import { RawValueMap } from 'fyo/core/types';
|
||||
import { groupBy } from 'lodash';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import { reports } from 'reports';
|
||||
import { OptionField } from 'schemas/types';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import { getEntryRoute } from 'src/router';
|
||||
import { GetAllOptions } from 'utils/db/types';
|
||||
import { fuzzyMatch } from '.';
|
||||
import { routeTo } from './ui';
|
||||
@ -31,6 +32,45 @@ interface DocSearchItem extends SearchItem {
|
||||
more: string[];
|
||||
}
|
||||
|
||||
type SearchItems = (DocSearchItem | SearchItem)[];
|
||||
|
||||
interface Searchable {
|
||||
needsUpdate: boolean;
|
||||
schemaName: string;
|
||||
fields: string[];
|
||||
meta: string[];
|
||||
isChild: boolean;
|
||||
isSubmittable: boolean;
|
||||
}
|
||||
|
||||
interface Keyword {
|
||||
values: string[];
|
||||
meta: Record<string, string | boolean | undefined>;
|
||||
priority: number;
|
||||
}
|
||||
|
||||
interface SearchFilters {
|
||||
groupFilters: Record<SearchGroup, boolean>;
|
||||
skipTables: boolean;
|
||||
skipTransactions: boolean;
|
||||
schemaFilters: Record<string, boolean>;
|
||||
}
|
||||
|
||||
interface SearchIntermediate {
|
||||
suggestions: SearchItems;
|
||||
previousInput?: string;
|
||||
}
|
||||
|
||||
export function getGroupLabelMap() {
|
||||
return {
|
||||
Create: t`Create`,
|
||||
List: t`List`,
|
||||
Report: t`Report`,
|
||||
Docs: t`Docs`,
|
||||
Page: t`Page`,
|
||||
};
|
||||
}
|
||||
|
||||
async function openQuickEditDoc(schemaName: string) {
|
||||
await routeTo(`/list/${schemaName}`);
|
||||
const doc = await fyo.doc.getNewDoc(schemaName);
|
||||
@ -228,36 +268,23 @@ function getSetupList(): SearchItem[] {
|
||||
];
|
||||
}
|
||||
|
||||
export function getSearchList() {
|
||||
return [
|
||||
getListViewList(),
|
||||
getCreateList(),
|
||||
getReportList(),
|
||||
getSetupList(),
|
||||
].flat();
|
||||
}
|
||||
|
||||
interface Searchable {
|
||||
schemaName: string;
|
||||
fields: string[];
|
||||
meta: string[];
|
||||
isChild: boolean;
|
||||
isSubmittable: boolean;
|
||||
}
|
||||
|
||||
interface Keyword {
|
||||
values: string[];
|
||||
meta: Record<string, string | boolean | undefined>;
|
||||
priority: number;
|
||||
}
|
||||
|
||||
interface Keywords {
|
||||
searchable: Searchable;
|
||||
keywords: Keyword[];
|
||||
function getNonDocSearchList() {
|
||||
return [getListViewList(), getCreateList(), getReportList(), getSetupList()]
|
||||
.flat()
|
||||
.map((d) => {
|
||||
if (d.route && !d.action) {
|
||||
d.action = () => {
|
||||
routeTo(d.route!);
|
||||
};
|
||||
}
|
||||
return d;
|
||||
});
|
||||
}
|
||||
|
||||
class Search {
|
||||
keywords: Record<string, Keywords>;
|
||||
_obsSet: boolean = false;
|
||||
searchables: Record<string, Searchable>;
|
||||
keywords: Record<string, Keyword[]>;
|
||||
priorityMap: Record<string, number> = {
|
||||
[ModelNameEnum.SalesInvoice]: 125,
|
||||
[ModelNameEnum.PurchaseInvoice]: 100,
|
||||
@ -267,67 +294,50 @@ class Search {
|
||||
[ModelNameEnum.JournalEntry]: 50,
|
||||
};
|
||||
|
||||
_groupedKeywords?: Dictionary<Keyword[]>;
|
||||
filters: SearchFilters = {
|
||||
groupFilters: {
|
||||
List: true,
|
||||
Report: true,
|
||||
Create: true,
|
||||
Page: true,
|
||||
Docs: true,
|
||||
},
|
||||
schemaFilters: {},
|
||||
skipTables: false,
|
||||
skipTransactions: false,
|
||||
};
|
||||
|
||||
_intermediate: SearchIntermediate = { suggestions: [] };
|
||||
|
||||
_nonDocSearchList: SearchItem[];
|
||||
_groupLabelMap?: Record<SearchGroup, string>;
|
||||
|
||||
constructor() {
|
||||
this.keywords = {};
|
||||
this.searchables = {};
|
||||
this._nonDocSearchList = getNonDocSearchList();
|
||||
}
|
||||
|
||||
get groupedKeywords() {
|
||||
if (!this._groupedKeywords || !Object.keys(this._groupedKeywords!).length) {
|
||||
this._groupedKeywords = this.getGroupedKeywords();
|
||||
}
|
||||
|
||||
return this._groupedKeywords!;
|
||||
async initializeKeywords() {
|
||||
this._setSearchables();
|
||||
await this.updateKeywords();
|
||||
this._setDocObservers();
|
||||
this._setSchemaFilters();
|
||||
this._groupLabelMap = getGroupLabelMap();
|
||||
}
|
||||
|
||||
search(keyword: string /*, array: DocSearchItem[]*/): DocSearchItem[] {
|
||||
const array: DocSearchItem[] = [];
|
||||
if (!keyword) {
|
||||
return [];
|
||||
_setSchemaFilters() {
|
||||
for (const name in this.searchables) {
|
||||
this.filters.schemaFilters[name] = true;
|
||||
}
|
||||
}
|
||||
|
||||
const groupedKeywords = this.groupedKeywords;
|
||||
const keys = Object.keys(groupedKeywords).sort(
|
||||
(a, b) => parseFloat(b) - parseFloat(a)
|
||||
);
|
||||
|
||||
for (const key of keys) {
|
||||
for (const kw of groupedKeywords[key]) {
|
||||
let isMatch = false;
|
||||
for (const word of kw.values) {
|
||||
isMatch ||= fuzzyMatch(keyword, word).isMatch;
|
||||
if (isMatch) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isMatch) {
|
||||
continue;
|
||||
}
|
||||
|
||||
array.push({
|
||||
label: kw.values[0],
|
||||
schemaLabel: fyo.schemaMap[kw.meta.schemaName as string]?.label!,
|
||||
more: kw.values.slice(1),
|
||||
group: 'Docs',
|
||||
action: () => {
|
||||
console.log('selected', kw);
|
||||
},
|
||||
});
|
||||
async updateKeywords() {
|
||||
for (const searchable of Object.values(this.searchables)) {
|
||||
if (!searchable.needsUpdate) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
getGroupedKeywords() {
|
||||
const keywords = Object.values(this.keywords);
|
||||
return groupBy(keywords.map((kw) => kw.keywords).flat(), 'priority');
|
||||
}
|
||||
|
||||
async fetchKeywords() {
|
||||
const searchables = this._getSearchables();
|
||||
for (const searchable of searchables) {
|
||||
const options: GetAllOptions = {
|
||||
fields: [searchable.fields, searchable.meta].flat(),
|
||||
order: 'desc',
|
||||
@ -338,17 +348,264 @@ class Search {
|
||||
}
|
||||
|
||||
const maps = await fyo.db.getAllRaw(searchable.schemaName, options);
|
||||
this._addToSearchable(maps, searchable);
|
||||
this._setKeywords(maps, searchable);
|
||||
this.searchables[searchable.schemaName].needsUpdate = false;
|
||||
}
|
||||
|
||||
this._setPriority();
|
||||
}
|
||||
|
||||
_getSearchables(): Searchable[] {
|
||||
const searchable: Searchable[] = [];
|
||||
searchSuggestions(input: string): SearchItems {
|
||||
const matches: { si: SearchItem | DocSearchItem; distance: number }[] = [];
|
||||
|
||||
for (const si of this._intermediate.suggestions) {
|
||||
const label = si.label;
|
||||
const groupLabel =
|
||||
(si as DocSearchItem).schemaLabel || this._groupLabelMap![si.group];
|
||||
const more = (si as DocSearchItem).more ?? [];
|
||||
const values = [label, more, groupLabel].flat();
|
||||
|
||||
const { isMatch, distance } = this._getMatchAndDistance(input, values);
|
||||
|
||||
if (isMatch) {
|
||||
matches.push({ si, distance });
|
||||
}
|
||||
}
|
||||
|
||||
matches.sort((a, b) => a.distance - b.distance);
|
||||
const suggestions = matches.map((m) => m.si);
|
||||
console.log('here', suggestions.length, input);
|
||||
this.setIntermediate(suggestions, input);
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
shouldUseSuggestions(input?: string): boolean {
|
||||
if (!input) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { suggestions, previousInput } = this._intermediate;
|
||||
if (!suggestions?.length || !previousInput) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!input.startsWith(previousInput)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
setIntermediate(suggestions: SearchItems, previousInput?: string) {
|
||||
this._intermediate.suggestions = suggestions;
|
||||
this._intermediate.previousInput = previousInput;
|
||||
}
|
||||
|
||||
search(input?: string): SearchItems {
|
||||
const useSuggestions = this.shouldUseSuggestions(input);
|
||||
/*
|
||||
console.log(
|
||||
input,
|
||||
this._intermediate.previousInput,
|
||||
useSuggestions,
|
||||
this._intermediate.suggestions.length
|
||||
);
|
||||
*/
|
||||
|
||||
/**
|
||||
* If the suggestion list is already populated
|
||||
* and the input is an extention of the previous
|
||||
* then use the suggestions.
|
||||
*/
|
||||
if (useSuggestions) {
|
||||
// return this.searchSuggestions(input!);
|
||||
} else {
|
||||
this.setIntermediate([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the suggestion list.
|
||||
*/
|
||||
const groupedKeywords = this._getGroupedKeywords();
|
||||
const keys = Object.keys(groupedKeywords);
|
||||
if (!keys.includes('0')) {
|
||||
keys.push('0');
|
||||
}
|
||||
|
||||
keys.sort((a, b) => parseFloat(b) - parseFloat(a));
|
||||
const array: SearchItems = [];
|
||||
for (const key of keys) {
|
||||
this._pushDocSearchItems(groupedKeywords[key], array, input);
|
||||
if (key === '0') {
|
||||
this._pushNonDocSearchItems(array, input);
|
||||
}
|
||||
}
|
||||
|
||||
this.setIntermediate(array, input);
|
||||
return array;
|
||||
}
|
||||
|
||||
_pushDocSearchItems(keywords: Keyword[], array: SearchItems, input?: string) {
|
||||
if (!input) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.filters.groupFilters.Docs) {
|
||||
return;
|
||||
}
|
||||
|
||||
const subArray = this._getSubSortedArray(
|
||||
keywords,
|
||||
input
|
||||
) as DocSearchItem[];
|
||||
array.push(...subArray);
|
||||
}
|
||||
|
||||
_pushNonDocSearchItems(array: SearchItems, input?: string) {
|
||||
const filtered = this._nonDocSearchList.filter(
|
||||
(si) => this.filters.groupFilters[si.group]
|
||||
);
|
||||
const subArray = this._getSubSortedArray(filtered, input) as SearchItem[];
|
||||
array.push(...subArray);
|
||||
}
|
||||
|
||||
_getSubSortedArray(
|
||||
items: (SearchItem | Keyword)[],
|
||||
input?: string
|
||||
): SearchItems {
|
||||
const subArray: { item: SearchItem | DocSearchItem; distance: number }[] =
|
||||
[];
|
||||
|
||||
for (const item of items) {
|
||||
const isSearchItem = !!(item as SearchItem).group;
|
||||
|
||||
if (!input && isSearchItem) {
|
||||
subArray.push({ item: item as SearchItem, distance: 0 });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!input) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const values = this._getValueList(item).filter(Boolean);
|
||||
const { isMatch, distance } = this._getMatchAndDistance(input, values);
|
||||
|
||||
if (!isMatch) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isSearchItem) {
|
||||
subArray.push({ item: item as SearchItem, distance });
|
||||
} else {
|
||||
subArray.push({
|
||||
item: this._getDocSearchItemFromKeyword(item as Keyword),
|
||||
distance,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
subArray.sort((a, b) => a.distance - b.distance);
|
||||
return subArray.map(({ item }) => item);
|
||||
}
|
||||
|
||||
_getMatchAndDistance(input: string, values: string[]) {
|
||||
/**
|
||||
* All the parts should match with something.
|
||||
*/
|
||||
|
||||
let distance = Number.MAX_SAFE_INTEGER;
|
||||
for (const part of input.split(' ')) {
|
||||
const match = this._getInternalMatch(part, values);
|
||||
if (!match.isMatch) {
|
||||
return { isMatch: false, distance: Number.MAX_SAFE_INTEGER };
|
||||
}
|
||||
|
||||
distance = match.distance < distance ? match.distance : distance;
|
||||
}
|
||||
|
||||
return { isMatch: true, distance };
|
||||
}
|
||||
|
||||
_getInternalMatch(input: string, values: string[]) {
|
||||
let isMatch = false;
|
||||
let distance = Number.MAX_SAFE_INTEGER;
|
||||
|
||||
for (const k of values) {
|
||||
const match = fuzzyMatch(input, k);
|
||||
isMatch ||= match.isMatch;
|
||||
|
||||
if (match.distance < distance) {
|
||||
distance = match.distance;
|
||||
}
|
||||
}
|
||||
|
||||
return { isMatch, distance };
|
||||
}
|
||||
|
||||
_getValueList(item: SearchItem | Keyword): string[] {
|
||||
const { label, group } = item as SearchItem;
|
||||
if (group && group !== 'Docs') {
|
||||
return [label, group];
|
||||
}
|
||||
|
||||
const { values, meta } = item as Keyword;
|
||||
const schemaLabel = meta.schemaName as string;
|
||||
return [values, schemaLabel].flat();
|
||||
}
|
||||
|
||||
_getDocSearchItemFromKeyword(keyword: Keyword): DocSearchItem {
|
||||
const schemaName = keyword.meta.schemaName as string;
|
||||
const schemaLabel = fyo.schemaMap[schemaName]?.label!;
|
||||
const route = this._getRouteFromKeyword(keyword);
|
||||
return {
|
||||
label: keyword.values[0],
|
||||
schemaLabel,
|
||||
more: keyword.values.slice(1),
|
||||
group: 'Docs',
|
||||
action: async () => {
|
||||
await routeTo(route);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
_getRouteFromKeyword(keyword: Keyword): string {
|
||||
const { parent, parentSchemaName, schemaName } = keyword.meta;
|
||||
if (parent && parentSchemaName) {
|
||||
return getEntryRoute(parentSchemaName as string, parent as string);
|
||||
}
|
||||
|
||||
return getEntryRoute(schemaName as string, keyword.values[0]);
|
||||
}
|
||||
|
||||
_getGroupedKeywords() {
|
||||
/**
|
||||
* filter out the ignored groups
|
||||
* group by the keyword priority
|
||||
*/
|
||||
const keywords: Keyword[] = [];
|
||||
const schemaNames = Object.keys(this.keywords);
|
||||
for (const sn of schemaNames) {
|
||||
const searchable = this.searchables[sn];
|
||||
if (!this.filters.schemaFilters[sn] || !this.filters.groupFilters.Docs) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (searchable.isChild && this.filters.skipTables) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (searchable.isSubmittable && this.filters.skipTransactions) {
|
||||
continue;
|
||||
}
|
||||
keywords.push(...this.keywords[sn]);
|
||||
}
|
||||
|
||||
return groupBy(keywords.flat(), 'priority');
|
||||
}
|
||||
|
||||
_setSearchables() {
|
||||
for (const schemaName of Object.keys(fyo.schemaMap)) {
|
||||
const schema = fyo.schemaMap[schemaName];
|
||||
if (!schema?.keywordFields?.length) {
|
||||
if (!schema?.keywordFields?.length || this.searchables[schemaName]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -362,61 +619,62 @@ class Search {
|
||||
meta.push('submitted', 'cancelled');
|
||||
}
|
||||
|
||||
searchable.push({
|
||||
this.searchables[schemaName] = {
|
||||
schemaName,
|
||||
fields,
|
||||
meta,
|
||||
isChild: !!schema.isChild,
|
||||
isSubmittable: !!schema.isSubmittable,
|
||||
needsUpdate: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
_setDocObservers() {
|
||||
if (this._obsSet) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const { schemaName } of Object.values(this.searchables)) {
|
||||
fyo.doc.observer.on(`sync:${schemaName}`, () => {
|
||||
this.searchables[schemaName].needsUpdate = true;
|
||||
});
|
||||
|
||||
fyo.doc.observer.on(`delete:${schemaName}`, () => {
|
||||
this.searchables[schemaName].needsUpdate = true;
|
||||
});
|
||||
}
|
||||
|
||||
return searchable;
|
||||
this._obsSet = true;
|
||||
}
|
||||
|
||||
_setPriority() {
|
||||
for (const schemaName in this.keywords) {
|
||||
const kw = this.keywords[schemaName];
|
||||
const basePriority = this.priorityMap[schemaName] ?? 0;
|
||||
|
||||
for (const k of kw.keywords) {
|
||||
k.priority += basePriority;
|
||||
|
||||
if (k.meta.submitted) {
|
||||
k.priority += 25;
|
||||
}
|
||||
|
||||
if (k.meta.cancelled) {
|
||||
k.priority -= 200;
|
||||
}
|
||||
|
||||
if (kw.searchable.isChild) {
|
||||
k.priority -= 150;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_addToSearchable(maps: DocValueMap[], searchable: Searchable) {
|
||||
_setKeywords(maps: RawValueMap[], searchable: Searchable) {
|
||||
if (!maps.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.keywords[searchable.schemaName] ??= { searchable, keywords: [] };
|
||||
this.keywords[searchable.schemaName] = [];
|
||||
|
||||
for (const map of maps) {
|
||||
const keyword: Keyword = { values: [], meta: {}, priority: 0 };
|
||||
this._setKeywords(map, searchable, keyword);
|
||||
this._setKeywordValues(map, searchable, keyword);
|
||||
this._setMeta(map, searchable, keyword);
|
||||
this.keywords[searchable.schemaName]!.keywords.push(keyword);
|
||||
this.keywords[searchable.schemaName]!.push(keyword);
|
||||
}
|
||||
|
||||
this._setPriority(searchable);
|
||||
}
|
||||
|
||||
_setKeywords(map: DocValueMap, searchable: Searchable, keyword: Keyword) {
|
||||
_setKeywordValues(
|
||||
map: RawValueMap,
|
||||
searchable: Searchable,
|
||||
keyword: Keyword
|
||||
) {
|
||||
// Set individual field values
|
||||
for (const fn of searchable.fields) {
|
||||
let value = map[fn] as string | undefined;
|
||||
const field = fyo.getField(searchable.schemaName, fn);
|
||||
|
||||
const { options } = field as OptionField;
|
||||
if (options) {
|
||||
value = options.find((o) => o.value === value)?.label ?? value;
|
||||
@ -426,7 +684,7 @@ class Search {
|
||||
}
|
||||
}
|
||||
|
||||
_setMeta(map: DocValueMap, searchable: Searchable, keyword: Keyword) {
|
||||
_setMeta(map: RawValueMap, searchable: Searchable, keyword: Keyword) {
|
||||
// Set the meta map
|
||||
for (const fn of searchable.meta) {
|
||||
const meta = map[fn];
|
||||
@ -438,12 +696,31 @@ class Search {
|
||||
}
|
||||
|
||||
keyword.meta.schemaName = searchable.schemaName;
|
||||
if (keyword.meta.parent) {
|
||||
keyword.values.unshift(keyword.meta.parent as string);
|
||||
}
|
||||
}
|
||||
|
||||
_setPriority(searchable: Searchable) {
|
||||
const keywords = this.keywords[searchable.schemaName] ?? [];
|
||||
const basePriority = this.priorityMap[searchable.schemaName] ?? 0;
|
||||
|
||||
for (const k of keywords) {
|
||||
k.priority += basePriority;
|
||||
|
||||
if (k.meta.submitted) {
|
||||
k.priority += 25;
|
||||
}
|
||||
|
||||
if (k.meta.cancelled) {
|
||||
k.priority -= 200;
|
||||
}
|
||||
|
||||
if (searchable.isChild) {
|
||||
k.priority -= 150;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const docSearch = new Search();
|
||||
|
||||
if (fyo.store.isDevelopment) {
|
||||
//@ts-ignore
|
||||
window.search = docSearch;
|
||||
}
|
||||
export const searcher = new Search();
|
||||
|
@ -129,7 +129,7 @@ export function openSettings(tab: SettingsTab) {
|
||||
}
|
||||
|
||||
export async function routeTo(route: string | RouteLocationRaw) {
|
||||
let routeOptions = route;
|
||||
const routeOptions = route;
|
||||
if (
|
||||
typeof route === 'string' &&
|
||||
route === router.currentRoute.value.fullPath
|
||||
@ -138,10 +138,10 @@ export async function routeTo(route: string | RouteLocationRaw) {
|
||||
}
|
||||
|
||||
if (typeof route === 'string') {
|
||||
routeOptions = { path: route };
|
||||
return await router.push(route);
|
||||
}
|
||||
|
||||
await router.push(routeOptions);
|
||||
return await router.push(routeOptions);
|
||||
}
|
||||
|
||||
export async function deleteDocWithPrompt(doc: Doc) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user