mirror of
https://github.com/badlogic/heissepreise.git
synced 2024-06-24 09:25:53 +02:00
Implement sorts in items-list, fix items-filter filtering.
This commit is contained in:
parent
5964cc5de2
commit
1bb276addb
|
@ -1,5 +1,5 @@
|
|||
<div class="flex justify-center gap-4 py-2 font-bold text-primary border border-primary/50 lg:rounded-b-xl">
|
||||
<a href="index.html">Suche</a>
|
||||
<a href="changes.html">Preisänderungen</a>
|
||||
<a href="carts.html">Warenkorb</a>
|
||||
<a href="carts.html">Warenkörbe</a>
|
||||
</div>
|
|
@ -5,6 +5,7 @@ require("./views");
|
|||
await model.load();
|
||||
const itemsFilter = document.querySelector("items-filter");
|
||||
const itemsList = document.querySelector("items-list");
|
||||
itemsList.elements.sort.value = "chain-and-name";
|
||||
itemsFilter.model = itemsList.model = model.items;
|
||||
itemsFilter.filter();
|
||||
})();
|
||||
|
|
156
site/knn.js
Normal file
156
site/knn.js
Normal file
|
@ -0,0 +1,156 @@
|
|||
const { stem, stopWords } = require("./stem");
|
||||
|
||||
function dotProduct(vector1, vector2) {
|
||||
let product = 0;
|
||||
for (const key in vector1) {
|
||||
if (vector2.hasOwnProperty(key)) {
|
||||
product += vector1[key] * vector2[key];
|
||||
}
|
||||
}
|
||||
return product;
|
||||
}
|
||||
exports.dotProduct = dotProduct;
|
||||
|
||||
function addVector(vector1, vector2) {
|
||||
for (const key in vector2) {
|
||||
vector1[key] = (vector1[key] || 0) + vector2[key];
|
||||
}
|
||||
}
|
||||
exports.addVector = addVector;
|
||||
|
||||
function scaleVector(vector, scalar) {
|
||||
for (const key in vector) {
|
||||
vector[key] *= scalar;
|
||||
}
|
||||
}
|
||||
exports.scaleVector = scaleVector;
|
||||
|
||||
function normalizeVector(vector) {
|
||||
const len = magnitude(vector);
|
||||
for (const key in vector) {
|
||||
vector[key] /= len;
|
||||
}
|
||||
}
|
||||
exports.normalizeVector = normalizeVector;
|
||||
|
||||
function magnitude(vector) {
|
||||
let sumOfSquares = 0;
|
||||
for (const key in vector) {
|
||||
sumOfSquares += vector[key] ** 2;
|
||||
}
|
||||
return Math.sqrt(sumOfSquares);
|
||||
}
|
||||
exports.magnitude = magnitude;
|
||||
|
||||
function findMostSimilarItem(refItem, items) {
|
||||
let maxSimilarity = -1;
|
||||
let similarItem = null;
|
||||
let similarItemIdx = -1;
|
||||
items.forEach((item, idx) => {
|
||||
let similarity = dotProduct(refItem.vector, item.vector);
|
||||
if (similarity > maxSimilarity) {
|
||||
maxSimilarity = similarity;
|
||||
similarItem = item;
|
||||
similarItemIdx = idx;
|
||||
}
|
||||
});
|
||||
return {
|
||||
similarity: maxSimilarity,
|
||||
item: similarItem,
|
||||
index: similarItemIdx,
|
||||
};
|
||||
}
|
||||
exports.findMostSimilarItem = findMostSimilarItem;
|
||||
|
||||
function findMostSimilarItems(refItem, items, k = 5, accept = (ref, item) => true) {
|
||||
let topSimilarItems = [];
|
||||
let topSimilarities = [];
|
||||
|
||||
items.forEach((item, idx) => {
|
||||
if (!accept(refItem, item)) return;
|
||||
let similarity = dotProduct(refItem.vector, item.vector);
|
||||
|
||||
if (topSimilarItems.length < k) {
|
||||
topSimilarItems.push(item);
|
||||
topSimilarities.push(similarity);
|
||||
} else {
|
||||
let minSimilarity = Math.min(...topSimilarities);
|
||||
let minIndex = topSimilarities.indexOf(minSimilarity);
|
||||
|
||||
if (similarity > minSimilarity) {
|
||||
topSimilarItems[minIndex] = item;
|
||||
topSimilarities[minIndex] = similarity;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let similarItemsWithIndices = topSimilarItems.map((item, index) => {
|
||||
return {
|
||||
similarity: topSimilarities[index],
|
||||
item: item,
|
||||
index: items.indexOf(item),
|
||||
};
|
||||
});
|
||||
|
||||
return similarItemsWithIndices;
|
||||
}
|
||||
exports.findMostSimilarItems = findMostSimilarItems;
|
||||
|
||||
function similaritySortItems(items) {
|
||||
if (items.length == 0) return items;
|
||||
sortedItems = [items.shift()];
|
||||
let refItem = sortedItems[0];
|
||||
while (items.length > 0) {
|
||||
const similarItem = findMostSimilarItem(refItem, items);
|
||||
sortedItems.push(similarItem.item);
|
||||
items.splice(similarItem.index, 1);
|
||||
refItem = similarItem.item;
|
||||
}
|
||||
return sortedItems;
|
||||
}
|
||||
exports.similaritySortItems = similaritySortItems;
|
||||
|
||||
const NGRAM = 4;
|
||||
function vectorizeTokens(tokens) {
|
||||
const vector = {};
|
||||
for (token of tokens) {
|
||||
if (token.length > NGRAM) {
|
||||
for (let i = 0; i < token.length - NGRAM; i++) {
|
||||
let trigram = token.substring(i, i + NGRAM);
|
||||
vector[trigram] = (vector[trigram] || 0) + 1;
|
||||
}
|
||||
} else {
|
||||
vector[token] = (vector[token] || 0) + 1;
|
||||
}
|
||||
}
|
||||
normalizeVector(vector);
|
||||
return vector;
|
||||
}
|
||||
exports.vectorizeTokens = vectorizeTokens;
|
||||
|
||||
function vectorizeItem(item, useUnit = true, useStem = true) {
|
||||
const isNumber = /^\d+\.\d+$/;
|
||||
let name = item.name
|
||||
.toLowerCase()
|
||||
.replace(/[^\w\s]|_/g, "")
|
||||
.replace("-", " ")
|
||||
.replace(",", " ");
|
||||
item.tokens = name
|
||||
.split(/\s+/)
|
||||
.filter((token) => !stopWords.includes(token))
|
||||
.filter((token) => !isNumber.test(token))
|
||||
.map((token) => (useStem ? stem(token) : token));
|
||||
if (useUnit) {
|
||||
if (item.quantity) item.tokens.push("" + item.quantity);
|
||||
if (item.unit) item.tokens.push(item.unit);
|
||||
}
|
||||
item.vector = vectorizeTokens(item.tokens);
|
||||
}
|
||||
exports.vectorizeItem = vectorizeItem;
|
||||
|
||||
function vectorizeItems(items, useUnit = true) {
|
||||
items.forEach((item) => {
|
||||
if (!item.vector) vectorizeItem(item, useUnit);
|
||||
});
|
||||
}
|
||||
exports.vectorizeItems = vectorizeItems;
|
|
@ -53,6 +53,10 @@ if (typeof window !== "undefined") {
|
|||
setupLiveEdit();
|
||||
}
|
||||
|
||||
exports.isMobile = () => {
|
||||
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||||
};
|
||||
|
||||
exports.today = () => {
|
||||
const currentDate = new Date();
|
||||
const year = currentDate.getFullYear();
|
||||
|
|
952
site/stem.js
Normal file
952
site/stem.js
Normal file
|
@ -0,0 +1,952 @@
|
|||
/* by Joder Illi, Snowball mailing list */
|
||||
function stem(word) {
|
||||
/*
|
||||
Put u and y between vowels into upper case
|
||||
*/
|
||||
word = word.replace(/([aeiouyäöü])u([aeiouyäöü])/g, "$1U$2");
|
||||
word = word.replace(/([aeiouyäöü])y([aeiouyäöü])/g, "$1Y$2");
|
||||
|
||||
/*
|
||||
and then do the following mappings,
|
||||
(a) replace ß with ss,
|
||||
(a) replace ae with ä, Not doing these,
|
||||
have trouble with diphtongs
|
||||
(a) replace oe with ö, Not doing these,
|
||||
have trouble with diphtongs
|
||||
(a) replace ue with ü unless preceded by q. Not doing these,
|
||||
have trouble with diphtongs
|
||||
So in quelle, ue is not mapped to ü because it follows q, and in
|
||||
feuer it is not mapped because the first part of the rule changes it to
|
||||
feUer, so the u is not found.
|
||||
*/
|
||||
word = word.replace(/ß/g, "ss");
|
||||
//word = word.replace(/ae/g, 'ä');
|
||||
//word = word.replace(/oe/g, 'ö');
|
||||
//word = word.replace(/([^q])ue/g, '$1ü');
|
||||
|
||||
/*
|
||||
R1 and R2 are first set up in the standard way (see the note on R1
|
||||
and R2), but then R1 is adjusted so that the region before it contains at
|
||||
least 3 letters.
|
||||
R1 is the region after the first non-vowel following a vowel, or is
|
||||
the null region at the end of the word if there is no such non-vowel.
|
||||
R2 is the region after the first non-vowel following a vowel in R1,
|
||||
or is the null region at the end of the word if there is no such non-vowel.
|
||||
*/
|
||||
|
||||
var r1Index = word.search(/[aeiouyäöü][^aeiouyäöü]/);
|
||||
var r1 = "";
|
||||
if (r1Index != -1) {
|
||||
r1Index += 2;
|
||||
r1 = word.substring(r1Index);
|
||||
}
|
||||
|
||||
var r2Index = -1;
|
||||
var r2 = "";
|
||||
|
||||
if (r1Index != -1) {
|
||||
var r2Index = r1.search(/[aeiouyäöü][^aeiouyäöü]/);
|
||||
if (r2Index != -1) {
|
||||
r2Index += 2;
|
||||
r2 = r1.substring(r2Index);
|
||||
r2Index += r1Index;
|
||||
} else {
|
||||
r2 = "";
|
||||
}
|
||||
}
|
||||
|
||||
if (r1Index != -1 && r1Index < 3) {
|
||||
r1Index = 3;
|
||||
r1 = word.substring(r1Index);
|
||||
}
|
||||
|
||||
/*
|
||||
Define a valid s-ending as one of b, d, f, g, h, k, l, m, n, r or t.
|
||||
Define a valid st-ending as the same list, excluding letter r.
|
||||
*/
|
||||
|
||||
/*
|
||||
Do each of steps 1, 2 and 3.
|
||||
*/
|
||||
|
||||
/*
|
||||
Step 1:
|
||||
Search for the longest among the following suffixes,
|
||||
(a) em ern er
|
||||
(b) e en es
|
||||
(c) s (preceded by a valid s-ending)
|
||||
*/
|
||||
var a1Index = word.search(/(em|ern|er)$/g);
|
||||
var b1Index = word.search(/(e|en|es)$/g);
|
||||
var c1Index = word.search(/([bdfghklmnrt]s)$/g);
|
||||
if (c1Index != -1) {
|
||||
c1Index++;
|
||||
}
|
||||
var index1 = 10000;
|
||||
var optionUsed1 = "";
|
||||
if (a1Index != -1 && a1Index < index1) {
|
||||
optionUsed1 = "a";
|
||||
index1 = a1Index;
|
||||
}
|
||||
if (b1Index != -1 && b1Index < index1) {
|
||||
optionUsed1 = "b";
|
||||
index1 = b1Index;
|
||||
}
|
||||
if (c1Index != -1 && c1Index < index1) {
|
||||
optionUsed1 = "c";
|
||||
index1 = c1Index;
|
||||
}
|
||||
|
||||
/*
|
||||
and delete if in R1. (Of course the letter of the valid s-ending is
|
||||
not necessarily in R1.) If an ending of group (b) is deleted, and the ending
|
||||
is preceded by niss, delete the final s.
|
||||
(For example, äckern -> äck, ackers -> acker, armes -> arm,
|
||||
bedürfnissen -> bedürfnis)
|
||||
*/
|
||||
|
||||
if (index1 != 10000 && r1Index != -1) {
|
||||
if (index1 >= r1Index) {
|
||||
word = word.substring(0, index1);
|
||||
if (optionUsed1 == "b") {
|
||||
if (word.search(/niss$/) != -1) {
|
||||
word = word.substring(0, word.length - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
Step 2:
|
||||
Search for the longest among the following suffixes,
|
||||
(a) en er est
|
||||
(b) st (preceded by a valid st-ending, itself preceded by at least 3
|
||||
letters)
|
||||
*/
|
||||
|
||||
var a2Index = word.search(/(en|er|est)$/g);
|
||||
var b2Index = word.search(/(.{3}[bdfghklmnt]st)$/g);
|
||||
if (b2Index != -1) {
|
||||
b2Index += 4;
|
||||
}
|
||||
|
||||
var index2 = 10000;
|
||||
var optionUsed2 = "";
|
||||
if (a2Index != -1 && a2Index < index2) {
|
||||
optionUsed2 = "a";
|
||||
index2 = a2Index;
|
||||
}
|
||||
if (b2Index != -1 && b2Index < index2) {
|
||||
optionUsed2 = "b";
|
||||
index2 = b2Index;
|
||||
}
|
||||
|
||||
/*
|
||||
and delete if in R1.
|
||||
(For example, derbsten -> derbst by step 1, and derbst -> derb by
|
||||
step 2, since b is a valid st-ending, and is preceded by just 3 letters)
|
||||
*/
|
||||
|
||||
if (index2 != 10000 && r1Index != -1) {
|
||||
if (index2 >= r1Index) {
|
||||
word = word.substring(0, index2);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Step 3: d-suffixes (*)
|
||||
Search for the longest among the following suffixes, and perform the
|
||||
action indicated.
|
||||
end ung
|
||||
delete if in R2
|
||||
if preceded by ig, delete if in R2 and not preceded by e
|
||||
ig ik isch
|
||||
delete if in R2 and not preceded by e
|
||||
lich heit
|
||||
delete if in R2
|
||||
if preceded by er or en, delete if in R1
|
||||
keit
|
||||
delete if in R2
|
||||
if preceded by lich or ig, delete if in R2
|
||||
*/
|
||||
|
||||
var a3Index = word.search(/(end|ung)$/g);
|
||||
var b3Index = word.search(/[^e](ig|ik|isch)$/g);
|
||||
var c3Index = word.search(/(lich|heit)$/g);
|
||||
var d3Index = word.search(/(keit)$/g);
|
||||
if (b3Index != -1) {
|
||||
b3Index++;
|
||||
}
|
||||
|
||||
var index3 = 10000;
|
||||
var optionUsed3 = "";
|
||||
if (a3Index != -1 && a3Index < index3) {
|
||||
optionUsed3 = "a";
|
||||
index3 = a3Index;
|
||||
}
|
||||
if (b3Index != -1 && b3Index < index3) {
|
||||
optionUsed3 = "b";
|
||||
index3 = b3Index;
|
||||
}
|
||||
if (c3Index != -1 && c3Index < index3) {
|
||||
optionUsed3 = "c";
|
||||
index3 = c3Index;
|
||||
}
|
||||
if (d3Index != -1 && d3Index < index3) {
|
||||
optionUsed3 = "d";
|
||||
index3 = d3Index;
|
||||
}
|
||||
|
||||
if (index3 != 10000 && r2Index != -1) {
|
||||
if (index3 >= r2Index) {
|
||||
word = word.substring(0, index3);
|
||||
var optionIndex = -1;
|
||||
var optionSubsrt = "";
|
||||
if (optionUsed3 == "a") {
|
||||
optionIndex = word.search(/[^e](ig)$/);
|
||||
if (optionIndex != -1) {
|
||||
optionIndex++;
|
||||
if (optionIndex >= r2Index) {
|
||||
word = word.substring(0, optionIndex);
|
||||
}
|
||||
}
|
||||
} else if (optionUsed3 == "c") {
|
||||
optionIndex = word.search(/(er|en)$/);
|
||||
if (optionIndex != -1) {
|
||||
if (optionIndex >= r1Index) {
|
||||
word = word.substring(0, optionIndex);
|
||||
}
|
||||
}
|
||||
} else if (optionUsed3 == "d") {
|
||||
optionIndex = word.search(/(lich|ig)$/);
|
||||
if (optionIndex != -1) {
|
||||
if (optionIndex >= r2Index) {
|
||||
word = word.substring(0, optionIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Finally,
|
||||
turn U and Y back into lower case, and remove the umlaut accent from
|
||||
a, o and u.
|
||||
*/
|
||||
word = word.replace(/U/g, "u");
|
||||
word = word.replace(/Y/g, "y");
|
||||
word = word.replace(/ä/g, "a");
|
||||
word = word.replace(/ö/g, "o");
|
||||
word = word.replace(/ü/g, "u");
|
||||
|
||||
return word;
|
||||
}
|
||||
exports.stem = stem;
|
||||
|
||||
exports.stopWords = [
|
||||
"ab",
|
||||
"aber",
|
||||
"alle",
|
||||
"allein",
|
||||
"allem",
|
||||
"allen",
|
||||
"aller",
|
||||
"allerdings",
|
||||
"allerlei",
|
||||
"alles",
|
||||
"allmählich",
|
||||
"allzu",
|
||||
"als",
|
||||
"alsbald",
|
||||
"also",
|
||||
"am",
|
||||
"an",
|
||||
"and",
|
||||
"ander",
|
||||
"andere",
|
||||
"anderem",
|
||||
"anderen",
|
||||
"anderer",
|
||||
"andererseits",
|
||||
"anderes",
|
||||
"anderm",
|
||||
"andern",
|
||||
"andernfalls",
|
||||
"anders",
|
||||
"anstatt",
|
||||
"auch",
|
||||
"auf",
|
||||
"aus",
|
||||
"ausgenommen",
|
||||
"ausser",
|
||||
"ausserdem",
|
||||
"außer",
|
||||
"außerdem",
|
||||
"außerhalb",
|
||||
"bald",
|
||||
"bei",
|
||||
"beide",
|
||||
"beiden",
|
||||
"beiderlei",
|
||||
"beides",
|
||||
"beim",
|
||||
"beinahe",
|
||||
"bereits",
|
||||
"besonders",
|
||||
"besser",
|
||||
"beträchtlich",
|
||||
"bevor",
|
||||
"bezüglich",
|
||||
"bin",
|
||||
"bis",
|
||||
"bisher",
|
||||
"bislang",
|
||||
"bist",
|
||||
"bloß",
|
||||
"bsp.",
|
||||
"bzw",
|
||||
"ca",
|
||||
"ca.",
|
||||
"content",
|
||||
"da",
|
||||
"dabei",
|
||||
"dadurch",
|
||||
"dafür",
|
||||
"dagegen",
|
||||
"daher",
|
||||
"dahin",
|
||||
"damals",
|
||||
"damit",
|
||||
"danach",
|
||||
"daneben",
|
||||
"dann",
|
||||
"daran",
|
||||
"darauf",
|
||||
"daraus",
|
||||
"darin",
|
||||
"darum",
|
||||
"darunter",
|
||||
"darüber",
|
||||
"darüberhinaus",
|
||||
"das",
|
||||
"dass",
|
||||
"dasselbe",
|
||||
"davon",
|
||||
"davor",
|
||||
"dazu",
|
||||
"daß",
|
||||
"dein",
|
||||
"deine",
|
||||
"deinem",
|
||||
"deinen",
|
||||
"deiner",
|
||||
"deines",
|
||||
"dem",
|
||||
"demnach",
|
||||
"demselben",
|
||||
"den",
|
||||
"denen",
|
||||
"denn",
|
||||
"dennoch",
|
||||
"denselben",
|
||||
"der",
|
||||
"derart",
|
||||
"derartig",
|
||||
"derem",
|
||||
"deren",
|
||||
"derer",
|
||||
"derjenige",
|
||||
"derjenigen",
|
||||
"derselbe",
|
||||
"derselben",
|
||||
"derzeit",
|
||||
"des",
|
||||
"deshalb",
|
||||
"desselben",
|
||||
"dessen",
|
||||
"desto",
|
||||
"deswegen",
|
||||
"dich",
|
||||
"die",
|
||||
"diejenige",
|
||||
"dies",
|
||||
"diese",
|
||||
"dieselbe",
|
||||
"dieselben",
|
||||
"diesem",
|
||||
"diesen",
|
||||
"dieser",
|
||||
"dieses",
|
||||
"diesseits",
|
||||
"dir",
|
||||
"direkt",
|
||||
"direkte",
|
||||
"direkten",
|
||||
"direkter",
|
||||
"doch",
|
||||
"dort",
|
||||
"dorther",
|
||||
"dorthin",
|
||||
"drauf",
|
||||
"drin",
|
||||
"drunter",
|
||||
"drüber",
|
||||
"du",
|
||||
"dunklen",
|
||||
"durch",
|
||||
"durchaus",
|
||||
"eben",
|
||||
"ebenfalls",
|
||||
"ebenso",
|
||||
"eher",
|
||||
"eigenen",
|
||||
"eigenes",
|
||||
"eigentlich",
|
||||
"ein",
|
||||
"eine",
|
||||
"einem",
|
||||
"einen",
|
||||
"einer",
|
||||
"einerseits",
|
||||
"eines",
|
||||
"einfach",
|
||||
"einführen",
|
||||
"einführte",
|
||||
"einführten",
|
||||
"eingesetzt",
|
||||
"einig",
|
||||
"einige",
|
||||
"einigem",
|
||||
"einigen",
|
||||
"einiger",
|
||||
"einigermaßen",
|
||||
"einiges",
|
||||
"einmal",
|
||||
"eins",
|
||||
"einseitig",
|
||||
"einseitige",
|
||||
"einseitigen",
|
||||
"einseitiger",
|
||||
"einst",
|
||||
"einstmals",
|
||||
"einzig",
|
||||
"entsprechend",
|
||||
"entweder",
|
||||
"er",
|
||||
"erst",
|
||||
"es",
|
||||
"etc",
|
||||
"etliche",
|
||||
"etwa",
|
||||
"etwas",
|
||||
"euch",
|
||||
"euer",
|
||||
"eure",
|
||||
"eurem",
|
||||
"euren",
|
||||
"eurer",
|
||||
"eures",
|
||||
"falls",
|
||||
"fast",
|
||||
"ferner",
|
||||
"folgende",
|
||||
"folgenden",
|
||||
"folgender",
|
||||
"folgendes",
|
||||
"folglich",
|
||||
"fuer",
|
||||
"für",
|
||||
"gab",
|
||||
"ganze",
|
||||
"ganzem",
|
||||
"ganzen",
|
||||
"ganzer",
|
||||
"ganzes",
|
||||
"gar",
|
||||
"gegen",
|
||||
"gemäss",
|
||||
"ggf",
|
||||
"gleich",
|
||||
"gleichwohl",
|
||||
"gleichzeitig",
|
||||
"glücklicherweise",
|
||||
"gänzlich",
|
||||
"hab",
|
||||
"habe",
|
||||
"haben",
|
||||
"haette",
|
||||
"hast",
|
||||
"hat",
|
||||
"hatte",
|
||||
"hatten",
|
||||
"hattest",
|
||||
"hattet",
|
||||
"heraus",
|
||||
"herein",
|
||||
"hier",
|
||||
"hier",
|
||||
"hinter",
|
||||
"hiermit",
|
||||
"hiesige",
|
||||
"hin",
|
||||
"hinein",
|
||||
"hinten",
|
||||
"hinter",
|
||||
"hinterher",
|
||||
"http",
|
||||
"hätt",
|
||||
"hätte",
|
||||
"hätten",
|
||||
"höchstens",
|
||||
"ich",
|
||||
"igitt",
|
||||
"ihm",
|
||||
"ihn",
|
||||
"ihnen",
|
||||
"ihr",
|
||||
"ihre",
|
||||
"ihrem",
|
||||
"ihren",
|
||||
"ihrer",
|
||||
"ihres",
|
||||
"im",
|
||||
"immer",
|
||||
"immerhin",
|
||||
"in",
|
||||
"indem",
|
||||
"indessen",
|
||||
"infolge",
|
||||
"innen",
|
||||
"innerhalb",
|
||||
"ins",
|
||||
"insofern",
|
||||
"inzwischen",
|
||||
"irgend",
|
||||
"irgendeine",
|
||||
"irgendwas",
|
||||
"irgendwen",
|
||||
"irgendwer",
|
||||
"irgendwie",
|
||||
"irgendwo",
|
||||
"ist",
|
||||
"ja",
|
||||
"je",
|
||||
"jed",
|
||||
"jede",
|
||||
"jedem",
|
||||
"jeden",
|
||||
"jedenfalls",
|
||||
"jeder",
|
||||
"jederlei",
|
||||
"jedes",
|
||||
"jedoch",
|
||||
"jemand",
|
||||
"jene",
|
||||
"jenem",
|
||||
"jenen",
|
||||
"jener",
|
||||
"jenes",
|
||||
"jenseits",
|
||||
"jetzt",
|
||||
"jährig",
|
||||
"jährige",
|
||||
"jährigen",
|
||||
"jähriges",
|
||||
"kam",
|
||||
"kann",
|
||||
"kannst",
|
||||
"kaum",
|
||||
"kein",
|
||||
"keine",
|
||||
"keinem",
|
||||
"keinen",
|
||||
"keiner",
|
||||
"keinerlei",
|
||||
"keines",
|
||||
"keineswegs",
|
||||
"klar",
|
||||
"klare",
|
||||
"klaren",
|
||||
"klares",
|
||||
"klein",
|
||||
"kleinen",
|
||||
"kleiner",
|
||||
"kleines",
|
||||
"koennen",
|
||||
"koennt",
|
||||
"koennte",
|
||||
"koennten",
|
||||
"komme",
|
||||
"kommen",
|
||||
"kommt",
|
||||
"konkret",
|
||||
"konkrete",
|
||||
"konkreten",
|
||||
"konkreter",
|
||||
"konkretes",
|
||||
"können",
|
||||
"könnt",
|
||||
"künftig",
|
||||
"leider",
|
||||
"machen",
|
||||
"man",
|
||||
"manche",
|
||||
"manchem",
|
||||
"manchen",
|
||||
"mancher",
|
||||
"mancherorts",
|
||||
"manches",
|
||||
"manchmal",
|
||||
"mehr",
|
||||
"mehrere",
|
||||
"mein",
|
||||
"meine",
|
||||
"meinem",
|
||||
"meinen",
|
||||
"meiner",
|
||||
"meines",
|
||||
"mich",
|
||||
"mir",
|
||||
"mit",
|
||||
"mithin",
|
||||
"muessen",
|
||||
"muesst",
|
||||
"muesste",
|
||||
"muss",
|
||||
"musst",
|
||||
"musste",
|
||||
"mussten",
|
||||
"muß",
|
||||
"mußt",
|
||||
"müssen",
|
||||
"müsste",
|
||||
"müssten",
|
||||
"müßt",
|
||||
"müßte",
|
||||
"nach",
|
||||
"nachdem",
|
||||
"nachher",
|
||||
"nachhinein",
|
||||
"nahm",
|
||||
"natürlich",
|
||||
"neben",
|
||||
"nebenan",
|
||||
"nehmen",
|
||||
"nein",
|
||||
"nicht",
|
||||
"nichts",
|
||||
"nie",
|
||||
"niemals",
|
||||
"niemand",
|
||||
"nirgends",
|
||||
"nirgendwo",
|
||||
"noch",
|
||||
"nun",
|
||||
"nur",
|
||||
"nächste",
|
||||
"nämlich",
|
||||
"nötigenfalls",
|
||||
"ob",
|
||||
"oben",
|
||||
"oberhalb",
|
||||
"obgleich",
|
||||
"obschon",
|
||||
"obwohl",
|
||||
"oder",
|
||||
"oft",
|
||||
"per",
|
||||
"plötzlich",
|
||||
"schließlich",
|
||||
"schon",
|
||||
"sehr",
|
||||
"sehrwohl",
|
||||
"seid",
|
||||
"sein",
|
||||
"seine",
|
||||
"seinem",
|
||||
"seinen",
|
||||
"seiner",
|
||||
"seines",
|
||||
"seit",
|
||||
"seitdem",
|
||||
"seither",
|
||||
"selber",
|
||||
"selbst",
|
||||
"sich",
|
||||
"sicher",
|
||||
"sicherlich",
|
||||
"sie",
|
||||
"sind",
|
||||
"so",
|
||||
"sobald",
|
||||
"sodass",
|
||||
"sofort",
|
||||
"sofern",
|
||||
"sog",
|
||||
"sogar",
|
||||
"solange",
|
||||
"solch",
|
||||
"solche",
|
||||
"solchem",
|
||||
"solchen",
|
||||
"solcher",
|
||||
"solches",
|
||||
"soll",
|
||||
"sollen",
|
||||
"sollst",
|
||||
"sollt",
|
||||
"sollte",
|
||||
"sollten",
|
||||
"somit",
|
||||
"sondern",
|
||||
"sonst",
|
||||
"sonstige",
|
||||
"sonstigen",
|
||||
"sonstiger",
|
||||
"sonstiges",
|
||||
"sooft",
|
||||
"soviel",
|
||||
"soweit",
|
||||
"sowie",
|
||||
"sowieso",
|
||||
"sowohl",
|
||||
"später",
|
||||
"statt",
|
||||
"stattfinden",
|
||||
"stattfand",
|
||||
"stattgefunden",
|
||||
"steht",
|
||||
"stets",
|
||||
"such",
|
||||
"suche",
|
||||
"suchen",
|
||||
"tatsächlich",
|
||||
"tatsächlichen",
|
||||
"tatsächlicher",
|
||||
"tatsächliches",
|
||||
"tatsächlich",
|
||||
"tatsächlichen",
|
||||
"tatsächlicher",
|
||||
"tatsächliches",
|
||||
"tief",
|
||||
"tiefer",
|
||||
"trotz",
|
||||
"trotzdem",
|
||||
"tun",
|
||||
"über",
|
||||
"überall",
|
||||
"überallhin",
|
||||
"überdies",
|
||||
"überhaupt",
|
||||
"übrig",
|
||||
"übrigens",
|
||||
"um",
|
||||
"umso",
|
||||
"umsoweniger",
|
||||
"unbedingt",
|
||||
"und",
|
||||
"unmöglich",
|
||||
"unnötig",
|
||||
"unser",
|
||||
"unsere",
|
||||
"unserem",
|
||||
"unseren",
|
||||
"unserer",
|
||||
"unseres",
|
||||
"unserseits",
|
||||
"unter",
|
||||
"unterhalb",
|
||||
"unterhalb",
|
||||
"untereinander",
|
||||
"untergebracht",
|
||||
"unterhalb",
|
||||
"unterhalb",
|
||||
"unterhalb",
|
||||
"unterhalb",
|
||||
"unterhalb",
|
||||
"unterhalb",
|
||||
"unterschiedlich",
|
||||
"unterschiedliche",
|
||||
"unterschiedlichen",
|
||||
"unterschiedlicher",
|
||||
"unterschiedliches",
|
||||
"unterschiedlich",
|
||||
"unterschiedliche",
|
||||
"unterschiedlichen",
|
||||
"unterschiedlicher",
|
||||
"unterschiedliches",
|
||||
"unzwar",
|
||||
"usw",
|
||||
"usw.",
|
||||
"vermag",
|
||||
"vermögen",
|
||||
"vermutlich",
|
||||
"verrate",
|
||||
"verraten",
|
||||
"verrätst",
|
||||
"verschieden",
|
||||
"verschiedene",
|
||||
"verschiedenen",
|
||||
"verschiedener",
|
||||
"verschiedenes",
|
||||
"versorgen",
|
||||
"versorgt",
|
||||
"versorgte",
|
||||
"versorgten",
|
||||
"viel",
|
||||
"viele",
|
||||
"vielem",
|
||||
"vielen",
|
||||
"vieler",
|
||||
"vieles",
|
||||
"vielleicht",
|
||||
"vielmals",
|
||||
"vier",
|
||||
"vierte",
|
||||
"viertel",
|
||||
"vierten",
|
||||
"vierter",
|
||||
"viertes",
|
||||
"vom",
|
||||
"von",
|
||||
"vor",
|
||||
"vorbei",
|
||||
"vorgestern",
|
||||
"vorher",
|
||||
"vorüber",
|
||||
"wach",
|
||||
"wachen",
|
||||
"wahrend",
|
||||
"wann",
|
||||
"war",
|
||||
"warauf",
|
||||
"ward",
|
||||
"waren",
|
||||
"warst",
|
||||
"wart",
|
||||
"warum",
|
||||
"was",
|
||||
"weder",
|
||||
"weil",
|
||||
"weiter",
|
||||
"weitere",
|
||||
"weiterem",
|
||||
"weiteren",
|
||||
"weiterer",
|
||||
"weiteres",
|
||||
"weiterhin",
|
||||
"weitgehend",
|
||||
"welche",
|
||||
"welchem",
|
||||
"welchen",
|
||||
"welcher",
|
||||
"welches",
|
||||
"wem",
|
||||
"wen",
|
||||
"wenig",
|
||||
"wenige",
|
||||
"wenigem",
|
||||
"wenigen",
|
||||
"weniger",
|
||||
"wenigstens",
|
||||
"wenn",
|
||||
"wenngleich",
|
||||
"wer",
|
||||
"werde",
|
||||
"werden",
|
||||
"werdet",
|
||||
"weshalb",
|
||||
"wessen",
|
||||
"wichtig",
|
||||
"wie",
|
||||
"wieder",
|
||||
"wiederum",
|
||||
"wieso",
|
||||
"will",
|
||||
"willst",
|
||||
"wir",
|
||||
"wird",
|
||||
"wirklich",
|
||||
"wirst",
|
||||
"wissen",
|
||||
"wo",
|
||||
"woanders",
|
||||
"wohl",
|
||||
"woher",
|
||||
"wohin",
|
||||
"wohingegen",
|
||||
"wohl",
|
||||
"wohlweislich",
|
||||
"wollen",
|
||||
"wollt",
|
||||
"wollte",
|
||||
"wollten",
|
||||
"womit",
|
||||
"woraufhin",
|
||||
"woraus",
|
||||
"woraussichtlich",
|
||||
"worauf",
|
||||
"woraus",
|
||||
"worin",
|
||||
"worüber",
|
||||
"wovon",
|
||||
"wovor",
|
||||
"wozu",
|
||||
"während",
|
||||
"währenddessen",
|
||||
"wär",
|
||||
"wäre",
|
||||
"wären",
|
||||
"wärst",
|
||||
"wäre",
|
||||
"wären",
|
||||
"wärst",
|
||||
"würde",
|
||||
"würden",
|
||||
"würdest",
|
||||
"würdet",
|
||||
"zB",
|
||||
"z.b.",
|
||||
"zehn",
|
||||
"zeigen",
|
||||
"zeitweise",
|
||||
"zu",
|
||||
"zufolge",
|
||||
"zugleich",
|
||||
"zuletzt",
|
||||
"zum",
|
||||
"zumal",
|
||||
"zumeist",
|
||||
"zunächst",
|
||||
"zur",
|
||||
"zurück",
|
||||
"zurückgehend",
|
||||
"zurückgehen",
|
||||
"zurückgegangen",
|
||||
"zurückgekommen",
|
||||
"zurückgekommen",
|
||||
"zurückgekommen",
|
||||
"zurückgekommen",
|
||||
"zurückgezogen",
|
||||
"zusammen",
|
||||
"zusätzlich",
|
||||
"zusammen",
|
||||
"zuvor",
|
||||
"zuviel",
|
||||
"zuweilen",
|
||||
"zwanzig",
|
||||
"zwar",
|
||||
"zwei",
|
||||
"zweite",
|
||||
"zweiten",
|
||||
"zweiter",
|
||||
"zweites",
|
||||
"zwischen",
|
||||
"zwischendurch",
|
||||
"zwölf",
|
||||
"überall",
|
||||
"überallhin",
|
||||
"überdies",
|
||||
"überhaupt",
|
||||
"übrig",
|
||||
"übrigens",
|
||||
];
|
|
@ -150,8 +150,22 @@ class ItemsFilter extends View {
|
|||
const decreased = elements.priceDecreased.checked;
|
||||
filteredItems = filteredItems.filter((item) => {
|
||||
if (item.priceHistory.length == 1) return false;
|
||||
if (increased && item.priceHistory[0].price > item.priceHistory[1].price) return true;
|
||||
if (decreased && item.priceHistory[0].price < item.priceHistory[1].price) return true;
|
||||
if (this._filterByPriceChanges && elements.priceChangesToday.checked) {
|
||||
const today = elements.priceChangesDate.value;
|
||||
for (let i = 0; i < item.priceHistory.length; i++) {
|
||||
if (item.priceHistory[i].date == today && i + 1 < item.priceHistory.length) {
|
||||
if (increased && item.priceHistory[i].price > item.priceHistory[i + 1].price) {
|
||||
return true;
|
||||
}
|
||||
if (decreased && item.priceHistory[i].price < item.priceHistory[i + 1].price) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (increased && item.priceHistory[0].price > item.priceHistory[1].price) return true;
|
||||
if (decreased && item.priceHistory[0].price < item.priceHistory[1].price) return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
@ -182,11 +196,11 @@ class ItemsFilter extends View {
|
|||
filteredItems = queryItems(query, filteredItems);
|
||||
}
|
||||
|
||||
console.log("Filtering items took " + (performance.now() - now) / 1000 + " secs");
|
||||
|
||||
this.model.removeListener(this._listener);
|
||||
this.model.filteredItems = filteredItems;
|
||||
this.model.addListener(this._listener);
|
||||
|
||||
console.log("Filtering items took " + (performance.now() - now) / 1000 + " secs");
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const { downloadJSON, dom, getDynamicElements, onVisibleOnce } = require("../misc");
|
||||
const { downloadJSON, dom, getDynamicElements, onVisibleOnce, isMobile } = require("../misc");
|
||||
const { vectorizeItems, similaritySortItems } = require("../knn");
|
||||
const { stores } = require("../model/stores");
|
||||
const { View } = require("./view");
|
||||
|
||||
|
@ -20,12 +21,13 @@ class ItemsList extends View {
|
|||
</div>
|
||||
<label>
|
||||
Sortieren
|
||||
<select x-id="sort">
|
||||
<select x-id="sort" x-change>
|
||||
<option value="price-asc">Preis aufsteigend</option>
|
||||
<option value="price-desc">Preis absteigend</option>
|
||||
<option value="quantity-asc">Menge aufsteigend</option>
|
||||
<option value="quantity-desc">Menge absteigend</option>
|
||||
<option value="name-similarity">Namensähnlichkeit</option>
|
||||
<option value="chain-and-name">Kette & Name</option>
|
||||
<option value="name-similarity" x-id="nameSimilarity" disabled>Namensähnlichkeit</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
@ -34,7 +36,7 @@ class ItemsList extends View {
|
|||
<tr class="bg-primary text-white hidden md:table-row uppercase text-sm">
|
||||
<th class="text-center">Kette</th>
|
||||
<th>Name</th>
|
||||
<th class="cursor-pointer">Preis <span x-id="expandPrices">+</span></th>
|
||||
<th x-id="expandPriceHistories" class="cursor-pointer">Preis +</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -63,6 +65,65 @@ class ItemsList extends View {
|
|||
</td>
|
||||
`
|
||||
);
|
||||
|
||||
const elements = this.elements;
|
||||
|
||||
elements.json.addEventListener("click", (event) => {
|
||||
event.preventDefault();
|
||||
if (!this.model) return;
|
||||
downloadJSON("items.json", this.model.filteredItems);
|
||||
});
|
||||
|
||||
// Cache in a field, so we don't have to call this.elements in each renderItem() call.
|
||||
this._showAllPriceHistories = false;
|
||||
elements.expandPriceHistories.addEventListener("click", () => {
|
||||
const showAll = (this._showAllPriceHistories = !this._showAllPriceHistories);
|
||||
elements.expandPriceHistories.innerText = showAll ? "Preis -" : "Preis +";
|
||||
elements.tableBody.querySelectorAll(".priceinfo").forEach((el) => (showAll ? el.classList.remove("hidden") : el.classList.add("hidden")));
|
||||
});
|
||||
|
||||
this.addEventListener("change", () => {
|
||||
this.render();
|
||||
});
|
||||
}
|
||||
|
||||
sort(items) {
|
||||
const sortType = this.elements.sort.value;
|
||||
if (sortType == "price-asc") {
|
||||
items.sort((a, b) => a.price - b.price);
|
||||
} else if (sortType == "price-desc") {
|
||||
items.sort((a, b) => b.price - a.price);
|
||||
} else if (sortType == "quantity-asc") {
|
||||
items.sort((a, b) => {
|
||||
if (a.unit != b.unit) return a.unit.localeCompare(b.unit);
|
||||
return a.quantity - b.quantity;
|
||||
});
|
||||
} else if (sortType == "quantity-desc") {
|
||||
items.sort((a, b) => {
|
||||
if (a.unit != b.unit) return a.unit.localeCompare(b.unit);
|
||||
return b.quantity - a.quantity;
|
||||
});
|
||||
} else if (sortType == "chain-and-name") {
|
||||
items.sort((a, b) => {
|
||||
if (a.store < b.store) {
|
||||
return -1;
|
||||
} else if (a.store > b.store) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (a.name < b.name) {
|
||||
return -1;
|
||||
} else if (a.name > b.name) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
} else {
|
||||
vectorizeItems(items);
|
||||
items = similaritySortItems(items);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
renderItem(item) {
|
||||
|
@ -139,12 +200,20 @@ class ItemsList extends View {
|
|||
}
|
||||
});
|
||||
});
|
||||
if (this._showAllPriceHistories) elements.priceHistory.classList.remove("hidden");
|
||||
return itemDom;
|
||||
}
|
||||
|
||||
render() {
|
||||
const items = this.model.filteredItems;
|
||||
const elements = this.elements;
|
||||
if (this.model.filteredItems.length != 0 && this.model.filteredItems.length <= (isMobile() ? 200 : 1000)) {
|
||||
elements.nameSimilarity.removeAttribute("disabled");
|
||||
} else {
|
||||
elements.nameSimilarity.setAttribute("disabled", "true");
|
||||
if (elements.sort.value == "name-similarity") elements.sort.value = "price-asc";
|
||||
}
|
||||
|
||||
const items = this.sort([...this.model.filteredItems]);
|
||||
elements.numItems.innerHTML =
|
||||
"<strong>Resultate:</strong> " + items.length + (this.model.totalItems > items.length ? " / " + this.model.totalItems : "");
|
||||
const tableBody = elements.tableBody;
|
||||
|
|
Loading…
Reference in New Issue
Block a user