aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTerry Truong <terry06890@gmail.com>2022-08-31 12:18:39 +1000
committerTerry Truong <terry06890@gmail.com>2022-08-31 12:45:03 +1000
commit8f4b899effcca7316a760b16773ebdc781215591 (patch)
tree6417e3424c3cdcccfa79224e30a05063fb7d4fcd
parent0cd58b3c1a8c5297579ea7a24a14d82ae8fed169 (diff)
Improve search suggestions
Don't show multiple suggestions for the same node Prioritise common-names Place suggestions from prefix-search before substring-search Add coloring to search-string matched-part and canonical-name-part
-rwxr-xr-xbackend/tilo.py82
-rw-r--r--src/components/SearchModal.vue15
-rw-r--r--src/lib.ts2
3 files changed, 53 insertions, 46 deletions
diff --git a/backend/tilo.py b/backend/tilo.py
index f08da80..00b3fc9 100755
--- a/backend/tilo.py
+++ b/backend/tilo.py
@@ -48,9 +48,9 @@ class TolNode:
class SearchSugg:
" Represents a search suggestion "
def __init__(self, name, canonicalName=None, pop=0):
- self.name = name # string
- self.canonicalName = canonicalName # string | null
- self.pop = pop # number
+ self.name = name # string
+ self.canonicalName = canonicalName # string | null
+ self.pop = pop if pop != None else 0 # number
class SearchSuggResponse:
" Sent as responses to 'sugg' requests "
def __init__(self, searchSuggs, hasMore):
@@ -146,48 +146,54 @@ def lookupSuggs(searchStr, suggLimit, tree, dbCur):
results = []
hasMore = False
# Get node names and alt-names, ordering by popularity
- query1, query2 = (None, None)
nodesTable = f"nodes_{getTableSuffix(tree)}"
- query1 = f"SELECT DISTINCT {nodesTable}.name, node_pop.pop FROM {nodesTable}" \
+ nameQuery = f"SELECT {nodesTable}.name, node_pop.pop FROM {nodesTable}" \
f" LEFT JOIN node_pop ON {nodesTable}.name = node_pop.name" \
f" WHERE node_pop.name LIKE ? AND node_pop.name NOT LIKE '[%'" \
- f" ORDER BY node_pop.pop DESC LIMIT ?"
- query2 = f"SELECT DISTINCT alt_name, names.name, node_pop.pop FROM" \
+ f" ORDER BY node_pop.pop DESC"
+ altNameQuery = f"SELECT alt_name, names.name, pref_alt, node_pop.pop FROM" \
f" names INNER JOIN {nodesTable} ON names.name = {nodesTable}.name" \
f" LEFT JOIN node_pop ON {nodesTable}.name = node_pop.name" \
- f" WHERE alt_name LIKE ? ORDER BY node_pop.pop DESC LIMIT ?"
- suggs = []
- for nodeName, pop in dbCur.execute(query1, (searchStr + "%", suggLimit + 1)):
- suggs.append(SearchSugg(nodeName, pop=pop))
- for altName, nodeName, pop in dbCur.execute(query2, (searchStr + "%", suggLimit + 1)):
- suggs.append(SearchSugg(altName, nodeName, pop))
+ f" WHERE alt_name LIKE ? ORDER BY node_pop.pop DESC"
+ suggs = {}
+ tempLimit = suggLimit + 1 # For determining if 'more suggestions exist'
+ # Prefix search
+ for altName, nodeName, prefAlt, pop in dbCur.execute(altNameQuery, (searchStr + "%",)):
+ if nodeName not in suggs or prefAlt == 1 and suggs[nodeName].canonicalName != None:
+ suggs[nodeName] = SearchSugg(altName, nodeName, pop)
+ if len(suggs) == tempLimit:
+ break
+ if len(suggs) < tempLimit:
+ # Prefix search of canonical names
+ for nodeName, pop in dbCur.execute(nameQuery, (searchStr + "%",)):
+ if nodeName not in suggs:
+ suggs[nodeName] = SearchSugg(nodeName, pop=pop)
+ if len(suggs) == tempLimit:
+ break
+ suggList = sorted(suggs.values(), key=lambda x: x.pop, reverse=True)
# If insufficient results, try substring-search
- foundNames = {n.name for n in suggs}
- suggs2 = []
- if len(suggs) < suggLimit:
- newLim = suggLimit + 1 - len(suggs)
- for nodeName, pop in dbCur.execute(query1, ("%" + searchStr + "%", newLim)):
- if nodeName not in foundNames:
- suggs2.append(SearchSugg(nodeName, pop=pop))
- foundNames.add(nodeName)
- if len(suggs) + len(suggs2) < suggLimit:
- newLim = suggLimit + 1 - len(suggs) - len(suggs2)
- for altName, nodeName, pop in dbCur.execute(query2, ("%" + searchStr + "%", suggLimit + 1)):
- if altName not in foundNames:
- suggs2.append(SearchSugg(altName, nodeName, pop))
- foundNames.add(altName)
- # Sort results
- suggs.sort(key=lambda x: x.name)
- suggs.sort(key=lambda x: x.pop, reverse=True)
- suggs2.sort(key=lambda x: x.name)
- suggs2.sort(key=lambda x: x.pop, reverse=True)
- suggs.extend(suggs2)
- # Apply suggestion-quantity limit
- results = suggs[:suggLimit]
- if len(suggs) > suggLimit:
- hasMore = True
+ if len(suggs) < tempLimit:
+ newNames = set()
+ oldNames = suggs.keys()
+ for altName, nodeName, prefAlt, pop in dbCur.execute(altNameQuery, ("%" + searchStr + "%",)):
+ if nodeName not in suggs or \
+ nodeName not in oldNames and prefAlt == 1 and suggs[nodeName].canonicalName != None:
+ suggs[nodeName] = SearchSugg(altName, nodeName, pop)
+ newNames.add(nodeName)
+ if len(suggs) == tempLimit:
+ break
+ if len(suggs) < tempLimit:
+ for nodeName, pop in dbCur.execute(nameQuery, ("%" + searchStr + "%",)):
+ if nodeName not in suggs:
+ suggs[nodeName] = SearchSugg(nodeName, pop=pop)
+ newNames.add(nodeName)
+ if len(suggs) == tempLimit:
+ break
+ suggList.extend(sorted([suggs[n] for n in newNames], key=lambda x: x.pop, reverse=True))
#
- return SearchSuggResponse(results, hasMore)
+ if len(suggList) > suggLimit:
+ hasMore = True
+ return SearchSuggResponse(suggList[:suggLimit], hasMore)
def lookupInfo(name, tree, dbCur):
" For a node name, returns an InfoResponse, or None "
# Get node info
diff --git a/src/components/SearchModal.vue b/src/components/SearchModal.vue
index 5d6345e..01db1ab 100644
--- a/src/components/SearchModal.vue
+++ b/src/components/SearchModal.vue
@@ -15,8 +15,9 @@
@click="resolveSearch(sugg.canonicalName || sugg.name)">
<div class="grow overflow-hidden whitespace-nowrap text-ellipsis">
<span>{{suggDisplayStrings[idx][0]}}</span>
- <span class="font-bold">{{suggDisplayStrings[idx][1]}}</span>
+ <span class="font-bold text-lime-600">{{suggDisplayStrings[idx][1]}}</span>
<span>{{suggDisplayStrings[idx][2]}}</span>
+ <span class="text-stone-500">{{suggDisplayStrings[idx][3]}}</span>
</div>
<info-icon class="hover:cursor-pointer my-auto w-5 h-5"
@click.stop="onInfoIconClick(sugg.canonicalName || sugg.name)"/>
@@ -90,22 +91,22 @@ export default defineComponent({
color: this.uiOpts.textColor,
};
},
- suggDisplayStrings(): [string, string, string][] {
- let result: [string, string, string][] = [];
+ suggDisplayStrings(): [string, string, string, string][] {
+ let result: [string, string, string, string][] = [];
let input = this.suggsInput.toLowerCase();
// For each SearchSugg
for (let sugg of this.searchSuggs){
let idx = sugg.name.indexOf(input);
// Split suggestion text into parts before/within/after an input match
- let strings: [string, string, string];
+ let strings: [string, string, string, string];
if (idx != -1){
- strings = [sugg.name.substring(0, idx), input, sugg.name.substring(idx + input.length)];
+ strings = [sugg.name.substring(0, idx), input, sugg.name.substring(idx + input.length), ''];
} else {
- strings = [input, '', ''];
+ strings = [input, '', '', ''];
}
// Indicate any distinct canonical-name
if (sugg.canonicalName != null){
- strings[2] += ` (aka ${sugg.canonicalName})`;
+ strings[3] = ` (aka ${sugg.canonicalName})`;
}
//
result.push(strings);
diff --git a/src/lib.ts b/src/lib.ts
index 821a2e1..bee7d6b 100644
--- a/src/lib.ts
+++ b/src/lib.ts
@@ -140,7 +140,7 @@ export function getDefaultUiOpts(lytOpts: LayoutOptions): UiOptions {
let bgColor = '#292524',
bgColorLight = '#44403c', bgColorDark = '#1c1917',
bgColorLight2 = '#57534e', bgColorDark2 = '#0e0c0b',
- bgColorAlt = '#f5f5f4', bgColorAltDark = '#a8a29e';
+ bgColorAlt = '#f5f5f4', bgColorAltDark = '#d6d3d1';
let altColor = '#a3e623', altColorDark = '#65a30d';
let accentColor = '#f59e0b';
let scrollGap = getScrollBarWidth();