mirror of
https://github.com/simon987/sist2.git
synced 2025-04-10 05:56:46 +00:00
614 lines
15 KiB
JavaScript
614 lines
15 KiB
JavaScript
const SIZE = 40;
|
|
let mimeMap = [];
|
|
let tagMap = [];
|
|
let mimeTree;
|
|
let tagTree;
|
|
|
|
let searchBar = document.getElementById("searchBar");
|
|
let pathBar = document.getElementById("pathBar");
|
|
let lastDoc = null;
|
|
let reachedEnd = false;
|
|
let docCount = 0;
|
|
let coolingDown = false;
|
|
let searchBusy = true;
|
|
let selectedIndices = [];
|
|
let indexMap = {};
|
|
|
|
const CONF = new Settings();
|
|
|
|
const _defaults = {
|
|
display: "grid",
|
|
fuzzy: true,
|
|
highlight: true
|
|
};
|
|
|
|
function Settings() {
|
|
this.options = {};
|
|
|
|
this._onUpdate = function () {
|
|
$("#fuzzyToggle").prop("checked", this.options.fuzzy);
|
|
}
|
|
|
|
this.load = function () {
|
|
const raw = window.localStorage.getItem("options");
|
|
if (raw === null) {
|
|
this.options = _defaults;
|
|
} else {
|
|
this.options = JSON.parse(raw);
|
|
}
|
|
|
|
this._onUpdate();
|
|
}
|
|
|
|
this.save = function () {
|
|
window.localStorage.setItem("options", JSON.stringify(this.options));
|
|
this._onUpdate();
|
|
}
|
|
}
|
|
|
|
|
|
function showEsError() {
|
|
$.toast({
|
|
heading: "Elasticsearch connection error",
|
|
text: "sist2 web module encountered an error while connecting " +
|
|
"to Elasticsearch. See server logs for more information.",
|
|
stack: false,
|
|
bgColor: "#a94442",
|
|
textColor: "#f2dede",
|
|
position: 'bottom-right',
|
|
hideAfter: false
|
|
});
|
|
}
|
|
|
|
jQuery["jsonPost"] = function (url, data) {
|
|
return jQuery.ajax({
|
|
url: url,
|
|
type: "post",
|
|
data: JSON.stringify(data),
|
|
contentType: "application/json"
|
|
}).fail(err => {
|
|
showEsError();
|
|
console.log(err);
|
|
});
|
|
};
|
|
|
|
window.onload = () => {
|
|
$("#theme").on("click", () => {
|
|
if (!document.cookie.includes("sist")) {
|
|
document.cookie = "sist=dark";
|
|
} else {
|
|
document.cookie = "sist=; Max-Age=-99999999;";
|
|
}
|
|
window.location.reload();
|
|
})
|
|
CONF.load();
|
|
};
|
|
|
|
function toggleFuzzy() {
|
|
searchDebounced();
|
|
}
|
|
|
|
$.jsonPost("i").then(resp => {
|
|
|
|
const urlIndices = (new URLSearchParams(location.search)).get("i");
|
|
resp["indices"].forEach(idx => {
|
|
indexMap[idx.id] = idx.name;
|
|
const opt = $("<option>")
|
|
.attr("value", idx.id)
|
|
.append(idx.name);
|
|
|
|
if (urlIndices) {
|
|
if (urlIndices.split(",").indexOf(idx.name) !== -1) {
|
|
opt.attr("selected", true);
|
|
selectedIndices.push(idx.id);
|
|
}
|
|
} else if (!idx.name.includes("(nsfw)")) {
|
|
opt.attr("selected", true);
|
|
selectedIndices.push(idx.id);
|
|
}
|
|
$("#indices").append(opt);
|
|
});
|
|
|
|
createPathTree("#pathTree");
|
|
});
|
|
|
|
function getDocumentInfo(id) {
|
|
return $.getJSON("d/" + id).fail(e => {
|
|
console.log(e);
|
|
showEsError();
|
|
})
|
|
}
|
|
|
|
function handleTreeClick(tree) {
|
|
return (event, node, handler) => {
|
|
event.preventTreeDefault();
|
|
|
|
if (node.id === "any") {
|
|
if (!node.itree.state.checked) {
|
|
tree.deselect();
|
|
}
|
|
} else {
|
|
tree.node("any").deselect();
|
|
}
|
|
|
|
handler();
|
|
searchDebounced();
|
|
}
|
|
}
|
|
|
|
//TODO: filter based on selected indexes, sort mime types
|
|
$.jsonPost("es", {
|
|
aggs: {
|
|
mimeTypes: {
|
|
terms: {
|
|
field: "mime",
|
|
size: 10000
|
|
}
|
|
}
|
|
},
|
|
size: 0,
|
|
}).then(resp => {
|
|
resp["aggregations"]["mimeTypes"]["buckets"].forEach(bucket => {
|
|
let tmp = bucket["key"].split("/");
|
|
let category = tmp[0];
|
|
let mime = tmp[1];
|
|
|
|
let category_exists = false;
|
|
|
|
let child = {
|
|
"id": bucket["key"],
|
|
"text": `${mime} (${bucket["doc_count"]})`
|
|
};
|
|
|
|
mimeMap.forEach(node => {
|
|
if (node.text === category) {
|
|
node.children.push(child);
|
|
category_exists = true;
|
|
}
|
|
});
|
|
|
|
if (!category_exists) {
|
|
mimeMap.push({"text": category, children: [child]});
|
|
}
|
|
});
|
|
mimeMap.push({"text": "All", "id": "any"});
|
|
|
|
mimeTree = new InspireTree({
|
|
selection: {
|
|
mode: 'checkbox'
|
|
},
|
|
data: mimeMap
|
|
});
|
|
new InspireTreeDOM(mimeTree, {
|
|
target: '#mimeTree'
|
|
});
|
|
mimeTree.on("node.click", handleTreeClick(mimeTree));
|
|
mimeTree.deselect();
|
|
mimeTree.node("any").select();
|
|
});
|
|
|
|
// Tags tree
|
|
$.jsonPost("es", {
|
|
aggs: {
|
|
tags: {
|
|
terms: {
|
|
field: "tag",
|
|
size: 10000
|
|
}
|
|
}
|
|
},
|
|
size: 0,
|
|
}).then(resp => {
|
|
resp["aggregations"]["tags"]["buckets"]
|
|
.sort((a, b) => a["key"].localeCompare(b["key"]))
|
|
.forEach(bucket => {
|
|
addTag(tagMap, bucket["key"], bucket["key"], bucket["doc_count"])
|
|
});
|
|
|
|
tagMap.push({"text": "All", "id": "any"});
|
|
tagTree = new InspireTree({
|
|
selection: {
|
|
mode: 'checkbox'
|
|
},
|
|
data: tagMap
|
|
});
|
|
new InspireTreeDOM(tagTree, {
|
|
target: '#tagTree'
|
|
});
|
|
tagTree.on("node.click", handleTreeClick(tagTree));
|
|
tagTree.node("any").select();
|
|
searchBusy = false;
|
|
});
|
|
|
|
function addTag(map, tag, id, count) {
|
|
let tags = tag.split("#")[0].split(".");
|
|
|
|
let child = {
|
|
id: id,
|
|
text: tags.length !== 1 ? tags[0] : `${tags[0]} (${count})`,
|
|
children: []
|
|
};
|
|
|
|
let found = false;
|
|
map.forEach(node => {
|
|
if (node.text === child.text) {
|
|
found = true;
|
|
if (tags.length !== 1) {
|
|
addTag(node.children, tags.slice(1).join("."), id, count);
|
|
}
|
|
}
|
|
});
|
|
if (!found) {
|
|
if (tags.length !== 1) {
|
|
addTag(child.children, tags.slice(1).join("."), id, count);
|
|
map.push(child);
|
|
} else {
|
|
map.push(child);
|
|
}
|
|
}
|
|
}
|
|
|
|
function insertHits(resultContainer, hits) {
|
|
for (let i = 0; i < hits.length; i++) {
|
|
|
|
if (CONF.options.display === "grid") {
|
|
resultContainer.appendChild(createDocCard(hits[i]));
|
|
} else {
|
|
resultContainer.appendChild(createDocLine(hits[i]));
|
|
}
|
|
docCount++;
|
|
}
|
|
}
|
|
|
|
window.addEventListener("scroll", function () {
|
|
if (!searchBusy) {
|
|
let threshold = 400;
|
|
|
|
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight - threshold) {
|
|
if (!reachedEnd) {
|
|
coolingDown = true;
|
|
search(lastDoc);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
function getSelectedNodes(tree) {
|
|
let selectedNodes = [];
|
|
|
|
let selected = tree.selected();
|
|
|
|
for (let i = 0; i < selected.length; i++) {
|
|
|
|
if (selected[i].id === "any") {
|
|
return ["any"]
|
|
}
|
|
|
|
//Only get children
|
|
if (selected[i].text.indexOf("(") !== -1) {
|
|
selectedNodes.push(selected[i].id);
|
|
}
|
|
}
|
|
|
|
return selectedNodes
|
|
}
|
|
|
|
function search(after = null) {
|
|
lastDoc = null;
|
|
|
|
if (searchBusy) {
|
|
return;
|
|
}
|
|
searchBusy = true;
|
|
|
|
let searchResults = document.getElementById("searchResults");
|
|
//Clear old search results
|
|
let preload;
|
|
if (!after) {
|
|
while (searchResults.firstChild) {
|
|
searchResults.removeChild(searchResults.firstChild);
|
|
}
|
|
preload = makePreloader();
|
|
searchResults.appendChild(preload);
|
|
}
|
|
|
|
let query = searchBar.value;
|
|
let empty = query === "";
|
|
let condition = empty ? "should" : "must";
|
|
let filters = [
|
|
{range: {size: {gte: size_min, lte: size_max}}},
|
|
{terms: {index: selectedIndices}}
|
|
];
|
|
let fields = [
|
|
"name^8",
|
|
"content^3",
|
|
"album^8", "artist^8", "title^8", "genre^2", "album_artist^8",
|
|
"font_name^6"
|
|
];
|
|
|
|
if ($("#fuzzyToggle").prop("checked")) {
|
|
fields.push("content.nGram");
|
|
fields.push("name.nGram^3");
|
|
}
|
|
|
|
let path = pathBar.value.replace(/\/$/, "").toLowerCase(); //remove trailing slashes
|
|
if (path !== "") {
|
|
filters.push([{term: {path: path}}])
|
|
}
|
|
let mimeTypes = getSelectedNodes(mimeTree);
|
|
if (!mimeTypes.includes("any")) {
|
|
filters.push([{terms: {"mime": mimeTypes}}]);
|
|
}
|
|
|
|
let tags = getSelectedNodes(tagTree);
|
|
if (!tags.includes("any")) {
|
|
filters.push([{terms: {"tag": tags}}]);
|
|
}
|
|
|
|
let q = {
|
|
"_source": {
|
|
excludes: ["content", "_tie"]
|
|
},
|
|
query: {
|
|
bool: {
|
|
[condition]: {
|
|
simple_query_string: {
|
|
query: query,
|
|
fields: fields,
|
|
default_operator: "and"
|
|
}
|
|
},
|
|
filter: filters
|
|
}
|
|
},
|
|
"sort": [
|
|
{"_score": {"order": "desc"}},
|
|
{"_tie": {"order": "asc"}}
|
|
],
|
|
aggs:
|
|
{
|
|
total_size: {"sum": {"field": "size"}},
|
|
total_count: {"value_count": {"field": "size"}}
|
|
},
|
|
size: SIZE,
|
|
};
|
|
|
|
if (after) {
|
|
q.search_after = [after["_score"], after["_id"]];
|
|
}
|
|
|
|
if (CONF.options.highlight) {
|
|
q.highlight = {
|
|
pre_tags: ["<mark>"],
|
|
post_tags: ["</mark>"],
|
|
fields: {
|
|
content: {},
|
|
// "content.nGram": {},
|
|
name: {},
|
|
"name.nGram": {},
|
|
font_name: {},
|
|
}
|
|
};
|
|
}
|
|
|
|
$.jsonPost("es", q).then(searchResult => {
|
|
let hits = searchResult["hits"]["hits"];
|
|
if (hits) {
|
|
lastDoc = hits[hits.length - 1];
|
|
}
|
|
|
|
if (!after) {
|
|
preload.remove();
|
|
searchResults.appendChild(makeStatsCard(searchResult));
|
|
} else {
|
|
let pageIndicator = makePageIndicator(searchResult);
|
|
searchResults.appendChild(pageIndicator);
|
|
}
|
|
|
|
//Setup page
|
|
let resultContainer = makeResultContainer();
|
|
searchResults.appendChild(resultContainer);
|
|
|
|
window.setTimeout(() => {
|
|
$(".sp").SmartPhoto({animationSpeed: 0, swipeTopToClose: true, showAnimation: false, forceInterval: 50});
|
|
}, 100);
|
|
|
|
if (!after) {
|
|
docCount = 0;
|
|
}
|
|
reachedEnd = hits.length !== SIZE;
|
|
insertHits(resultContainer, hits);
|
|
searchBusy = false;
|
|
});
|
|
}
|
|
|
|
let size_min = 0;
|
|
let size_max = 10000000000000;
|
|
|
|
let searchDebounced = _.debounce(function () {
|
|
coolingDown = false;
|
|
search()
|
|
}, 500);
|
|
|
|
searchBar.addEventListener("keyup", searchDebounced);
|
|
|
|
//Size slider
|
|
$("#sizeSlider").ionRangeSlider({
|
|
type: "double",
|
|
grid: false,
|
|
force_edges: true,
|
|
min: 0,
|
|
max: 3684.03149864,
|
|
from: 0,
|
|
to: 3684.03149864,
|
|
min_interval: 5,
|
|
drag_interval: true,
|
|
prettify: function (num) {
|
|
|
|
if (num === 0) {
|
|
return "0 B"
|
|
} else if (num >= 3684) {
|
|
return humanFileSize(num * num * num) + "+";
|
|
}
|
|
|
|
return humanFileSize(num * num * num)
|
|
},
|
|
onChange: function (e) {
|
|
size_min = (e.from * e.from * e.from);
|
|
size_max = (e.to * e.to * e.to);
|
|
|
|
if (e.to >= 3684) {
|
|
size_max = 10000000000000;
|
|
}
|
|
|
|
searchDebounced();
|
|
}
|
|
});
|
|
|
|
function updateIndices() {
|
|
let selected = $('#indices').find('option:selected');
|
|
selectedIndices = [];
|
|
$(selected).each(function () {
|
|
selectedIndices.push($(this).val());
|
|
});
|
|
|
|
searchDebounced();
|
|
}
|
|
|
|
document.getElementById("indices").addEventListener("change", updateIndices);
|
|
updateIndices();
|
|
|
|
window.onkeyup = function (e) {
|
|
if (e.key === "/" || e.key === "Escape") {
|
|
const bar = document.getElementById("searchBar");
|
|
bar.scrollIntoView();
|
|
bar.focus();
|
|
}
|
|
};
|
|
|
|
function getNextDepth(node) {
|
|
let q = {
|
|
query: {
|
|
bool: {
|
|
filter: [
|
|
{term: {index: node.index}},
|
|
{term: {_depth: node.depth + 1}}
|
|
]
|
|
}
|
|
},
|
|
aggs: {
|
|
paths: {
|
|
terms: {
|
|
field: "path",
|
|
size: 10000
|
|
}
|
|
}
|
|
},
|
|
size: 0
|
|
}
|
|
|
|
if (node.depth > 0) {
|
|
q.query.bool.must = {
|
|
prefix: {
|
|
path: node.id,
|
|
}
|
|
};
|
|
}
|
|
|
|
return $.jsonPost("es", q).then(resp => {
|
|
const buckets = resp["aggregations"]["paths"]["buckets"];
|
|
if (!buckets) {
|
|
return false;
|
|
}
|
|
return buckets
|
|
.filter(bucket => bucket.key.length > node.id.length || node.id.startsWith("/"))
|
|
.sort((a, b) => a.key > b.key)
|
|
.map(bucket => {
|
|
const i = bucket.key.lastIndexOf("/");
|
|
const name = (i === -1 || i === 1) ? bucket.key : bucket.key.slice(i + 1);
|
|
|
|
return {
|
|
id: bucket.key,
|
|
text: `${name}/ (${bucket.doc_count})`,
|
|
depth: node.depth + 1,
|
|
index: node.index,
|
|
children: true,
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
function handlePathTreeClick(tree) {
|
|
return (event, node, handler) => {
|
|
|
|
if (node.depth !== 0) {
|
|
$("#pathBar").val(node.id)
|
|
$("#pathTreeModal").modal("hide")
|
|
searchDebounced();
|
|
}
|
|
|
|
handler();
|
|
}
|
|
}
|
|
|
|
function createPathTree(target) {
|
|
let pathTree = new InspireTree({
|
|
data: function (node, resolve, reject) {
|
|
return getNextDepth(node);
|
|
}
|
|
});
|
|
|
|
selectedIndices.forEach(index => {
|
|
pathTree.addNode({
|
|
id: "/" + index,
|
|
text: `/[${indexMap[index]}]`,
|
|
index: index,
|
|
depth: 0,
|
|
children: true
|
|
})
|
|
})
|
|
|
|
new InspireTreeDOM(pathTree, {
|
|
target: target
|
|
});
|
|
|
|
pathTree.on("node.click", handlePathTreeClick(pathTree));
|
|
|
|
const button = document.querySelector("#pathBarHelper")
|
|
const tooltip = document.querySelector("#pathTreeTooltip")
|
|
console.log(button)
|
|
console.log(tooltip)
|
|
Popper.createPopper(button, tooltip ,{
|
|
trigger: "click",
|
|
placement: "right",
|
|
});
|
|
}
|
|
|
|
function updateSettings() {
|
|
CONF.options.display = $("#settingDisplay").val();
|
|
CONF.options.fuzzy = $("#settingFuzzy").prop("checked");
|
|
CONF.options.highlight = $("#settingHighlight").prop("checked");
|
|
CONF.save();
|
|
|
|
searchDebounced();
|
|
|
|
$.toast({
|
|
heading: "Settings updated",
|
|
text: "Settings saved to browser storage",
|
|
stack: 3,
|
|
bgColor: "#00a4bc",
|
|
textColor: "#fff",
|
|
position: 'bottom-right',
|
|
hideAfter: 3000,
|
|
loaderBg: "#08c7e8",
|
|
});
|
|
}
|
|
|
|
function loadSettings() {
|
|
CONF.load();
|
|
|
|
$("#settingDisplay").val(CONF.options.display);
|
|
$("#settingFuzzy").prop("checked", CONF.options.fuzzy);
|
|
$("#settingHighlight").prop("checked", CONF.options.highlight);
|
|
}
|