Compare commits
3
Commits
main
..
tag-search
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d699bd8b7e | ||
|
|
7411bc1583 | ||
|
|
6fa9f419d4 |
@@ -8,25 +8,38 @@
|
||||
|
||||
import Bookmark from '../bookmark/Template.svelte';
|
||||
|
||||
let fulltext = false;
|
||||
let counter = 0;
|
||||
const router = useTinyRouter();
|
||||
let counter = 9;
|
||||
let key = $derived(router.getQueryParam('key'));
|
||||
search.key = key;
|
||||
let bookmarks = $state(null);
|
||||
let companies = $state(null);
|
||||
let documents = $state(null);
|
||||
let fulltext = false;
|
||||
let notes = $state(null);
|
||||
let pages = $state(null);
|
||||
let projects = $state(null);
|
||||
let stock = $state(null);
|
||||
let tags = $state(null);
|
||||
let tasks = $state(null);
|
||||
let times = $state(null);
|
||||
|
||||
|
||||
|
||||
function doSearch(key){
|
||||
function doSearch(){
|
||||
if (counter > 0) return;
|
||||
warn(t('searching…'));
|
||||
counter = 10;
|
||||
bookmarks = null;
|
||||
companies = null;
|
||||
documents = null;
|
||||
notes = null;
|
||||
pages = null;
|
||||
projects = null;
|
||||
stock = null;
|
||||
tags = null;
|
||||
tasks = null;
|
||||
times = null;
|
||||
|
||||
const data = { key : key, fulltext : fulltext };
|
||||
let data = { key, fulltext : fulltext };
|
||||
post(api('bookmark/search'),data).then(handleBookmarks);
|
||||
post(api('company/search '),data).then(handleCompanies);
|
||||
post(api('document/search'),data).then(handleDocuments);
|
||||
@@ -110,6 +123,87 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function handleTags(resp){
|
||||
if (resp.ok){
|
||||
const json = await resp.json();
|
||||
let map = {};
|
||||
for (let [module, ids] of Object.entries(json)){
|
||||
let list = {};
|
||||
let path = module;
|
||||
map[module] = {};
|
||||
switch(module){
|
||||
case 'bookmark': {
|
||||
if (!bookmarks) bookmarks = {};
|
||||
list = bookmarks;
|
||||
break;
|
||||
}
|
||||
case 'company': {
|
||||
if (!companies) companies = {};
|
||||
list = companies;
|
||||
break;
|
||||
}
|
||||
case 'document': {
|
||||
if (!documents) documents = {};
|
||||
list = documents;
|
||||
break;
|
||||
}
|
||||
case 'note': {
|
||||
if (!notes) notes = {};
|
||||
list = notes;
|
||||
break;
|
||||
}
|
||||
case 'wiki': {
|
||||
if (!pages) pages = {};
|
||||
list = pages;
|
||||
break;
|
||||
}
|
||||
case 'project': {
|
||||
if (!projects) projects = {};
|
||||
list = projects;
|
||||
break;
|
||||
}
|
||||
case 'stock': {
|
||||
if (!stock) stock = { items : {}};
|
||||
list = stock.items;
|
||||
path = 'stock/item';
|
||||
break;
|
||||
}
|
||||
case 'task': {
|
||||
if (!tasks) tasks = {};
|
||||
list = tasks;
|
||||
break;
|
||||
}
|
||||
case 'time': {
|
||||
if (!times) times = {};
|
||||
list = times;
|
||||
break;
|
||||
}
|
||||
default:{
|
||||
console.warn("no handler for "+module+" with tag "+key);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
for (let id of ids){
|
||||
if (!list[id]) {
|
||||
const url = api(`${path}/${id}`);
|
||||
const resp = await get(url);
|
||||
if (resp.ok) {
|
||||
list[id] = await resp.json();
|
||||
} else {
|
||||
console.warn(`Failed to fetch ${module} ${id}: ${resp.statusText}`);
|
||||
}
|
||||
}
|
||||
map[module][id] = list[id];
|
||||
}
|
||||
}
|
||||
tags = map;
|
||||
} else {
|
||||
error(resp);
|
||||
}
|
||||
quitOne();
|
||||
}
|
||||
|
||||
async function handleTasks(resp){
|
||||
quitOne();
|
||||
if (resp.ok){
|
||||
@@ -142,16 +236,18 @@
|
||||
|
||||
function onclick(e){
|
||||
e.preventDefault();
|
||||
var target = e.target;
|
||||
while (target && !target.href) target=target.parentNode;
|
||||
let target = e.target;
|
||||
let href = target.getAttribute('href');
|
||||
if (href) router.navigate(href);
|
||||
target = target.getAttribute('target');
|
||||
if (target == '_blank') {
|
||||
window.open(href);
|
||||
} else if (href) router.navigate(href);
|
||||
return false;
|
||||
}
|
||||
|
||||
async function setKey(ev){
|
||||
if (ev) ev.preventDefault();
|
||||
doSearch(search.key);
|
||||
doSearch();
|
||||
}
|
||||
|
||||
async function setTitle(resp,key,module){
|
||||
@@ -169,26 +265,28 @@
|
||||
counter--;
|
||||
if (counter > 0) {
|
||||
warn(t('searching…')+" "+counter);
|
||||
if (counter == 1) {
|
||||
let data = { key, fulltext : fulltext };
|
||||
post(api('tags/search'),data).then(handleTags);
|
||||
}
|
||||
} else yikes();
|
||||
}
|
||||
|
||||
function updateUrl(ev){
|
||||
ev.preventDefault();
|
||||
let url = window.location.origin + window.location.pathname;
|
||||
if (search.key) {
|
||||
url += '?key=' + encodeURI(search.key);
|
||||
window.history.replaceState(history.state, '', url);
|
||||
doSearch(search.key);
|
||||
}
|
||||
if (search.key) router.navigate(window.location.pathname + '?key=' + encodeURI(search.key));
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
doSearch(router.getQueryParam('key'));
|
||||
key = router.getQueryParam('key');
|
||||
doSearch();
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Umbrella – {t('search')}{search.key?': '+search.key:''}</title>
|
||||
<title>Umbrella – {t('search')}{key?': '+key:''}</title>
|
||||
</svelte:head>
|
||||
|
||||
<fieldset class="search">
|
||||
@@ -205,6 +303,23 @@
|
||||
<button type="submit">{t('go')}</button>
|
||||
</form>
|
||||
</fieldset>
|
||||
{#if tags && Object.keys(tags).length}
|
||||
<fieldset>
|
||||
<legend>
|
||||
{t('tagged with "{tag}"',{tag:key})}
|
||||
</legend>
|
||||
{#each Object.entries(tags) as [module,list]}
|
||||
<h4>{t(module)}</h4>
|
||||
<ul>
|
||||
{#each Object.entries(list) as [id,item]}
|
||||
<li>
|
||||
<a target="_blank" href="/{module}/{id}/view" {onclick} >{item?.name?item.name:(item?.comment?.source?item.comment.source:(item?.type?.name?t(item.type.name)+" "+item?.number:t(module)+" "+id))}</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/each}
|
||||
</fieldset>
|
||||
{/if}
|
||||
{#if projects}
|
||||
<fieldset>
|
||||
<legend>
|
||||
@@ -213,7 +328,7 @@
|
||||
<ul>
|
||||
{#each Object.values(projects) as project}
|
||||
<li>
|
||||
<a href="/project/{project.id}/view" {onclick} >{project.name}</a>
|
||||
<a target="_blank" href="/project/{project.id}/view" {onclick} >{project.name}</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
@@ -227,7 +342,7 @@
|
||||
<ul>
|
||||
{#each Object.values(tasks) as task}
|
||||
<li>
|
||||
<a href="/task/{task.id}/view" {onclick} class={task.status.name}>{task.name}</a>
|
||||
<a target="_blank" href="/task/{task.id}/view" {onclick} class={task.status.name}>{task.name}</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
@@ -255,13 +370,13 @@
|
||||
<ul>
|
||||
{#each Object.values(companies) as company}
|
||||
<li>
|
||||
<a href="/company/{company.id}/view" {onclick} >{company.name}</a>
|
||||
<a target="_blank" href="/company/{company.id}/view" {onclick} >{company.name}</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</fieldset>
|
||||
{/if}
|
||||
{#if stock}
|
||||
{#if stock && (Object.keys(stock.items).length || Object.keys(stock.locations).length)}
|
||||
<fieldset>
|
||||
<legend>
|
||||
{t('stock')}
|
||||
@@ -271,7 +386,7 @@
|
||||
<ul>
|
||||
{#each Object.values(stock.items) as item}
|
||||
<li>
|
||||
<a href="/stock/{item.owner.type}/{item.owner.id}/item/{item.owner_number}" {onclick} >
|
||||
<a target="_blank" href="/stock/{item.owner.type}/{item.owner.id}/item/{item.owner_number}" {onclick} >
|
||||
{item.name} [{t('code')}: <span class="code">{item.code}</span>]
|
||||
</a>
|
||||
{@html item.description.rendered}
|
||||
@@ -284,7 +399,7 @@
|
||||
<ul>
|
||||
{#each Object.values(stock.locations) as loc (loc.id)}
|
||||
<li>
|
||||
<a href="/stock/location/{loc.id}">{loc.name}</a>
|
||||
<a target="_blank" href="/stock/location/{loc.id}">{loc.name}</a>
|
||||
<br/> {@html loc.description.rendered}
|
||||
</li>
|
||||
{/each}
|
||||
@@ -300,7 +415,7 @@
|
||||
<ul>
|
||||
{#each Object.values(times) as time}
|
||||
<li>
|
||||
<a href="/time/{time.id}/view" {onclick} >
|
||||
<a target="_blank" href="/time/{time.id}/view" {onclick} >
|
||||
{display(time.start_time)}{#if time.end_time}…{display(time.end_time)}{/if}
|
||||
{time.subject}
|
||||
</a>
|
||||
@@ -319,7 +434,7 @@
|
||||
<ul>
|
||||
{#each Object.values(documents) as document}
|
||||
<li>
|
||||
<a href="/document/{document.id}/view" {onclick} >
|
||||
<a target="_blank" href="/document/{document.id}/view" {onclick} >
|
||||
{document.number}
|
||||
({document.date})
|
||||
{document.sender.name.split('\n')[0]}
|
||||
@@ -339,7 +454,7 @@
|
||||
<ul>
|
||||
{#each Object.values(pages) as page}
|
||||
<li>
|
||||
<a href="/wiki/{page.id}/view" {onclick} >
|
||||
<a target="_blank" href="/wiki/{page.id}/view" {onclick} >
|
||||
{page.title}
|
||||
</a>
|
||||
</li>
|
||||
@@ -356,7 +471,7 @@
|
||||
{#each Object.values(notes) as note}
|
||||
<li>
|
||||
<b>
|
||||
<a href="/{note.module}/{note.entity_id}/view" {onclick} >{note.title}</a>
|
||||
<a target="_blank" href="/{note.module}/{note.entity_id}/view" {onclick} >{note.title}</a>
|
||||
</b>
|
||||
{@html target(note.text.rendered)}
|
||||
</li>
|
||||
@@ -364,3 +479,10 @@
|
||||
</ul>
|
||||
</fieldset>
|
||||
{/if}
|
||||
|
||||
<!--
|
||||
<pre>
|
||||
<code>
|
||||
{JSON.stringify(tags,null,2)}
|
||||
</code>
|
||||
</pre>-->
|
||||
@@ -4,6 +4,7 @@ package de.srsoftware.umbrella.tags;
|
||||
import static de.srsoftware.umbrella.core.ConnectionProvider.connect;
|
||||
import static de.srsoftware.umbrella.core.ModuleRegistry.userService;
|
||||
import static de.srsoftware.umbrella.core.ResponseCode.HTTP_UNPROCESSABLE;
|
||||
import static de.srsoftware.umbrella.core.constants.Field.KEY;
|
||||
import static de.srsoftware.umbrella.core.constants.Field.USER_LIST;
|
||||
import static de.srsoftware.umbrella.core.constants.Path.SEARCH;
|
||||
import static de.srsoftware.umbrella.core.constants.Path.USES;
|
||||
@@ -94,7 +95,18 @@ public class TagModule extends BaseHandler implements TagService {
|
||||
var user = userService().loadUser(token);
|
||||
if (user.isEmpty()) return unauthorized(ex);
|
||||
var module = path.pop();
|
||||
if (module == null) throw unprocessable("Module missing in path.");
|
||||
return switch (module) {
|
||||
case null -> throw unprocessable("Module missing in path.");
|
||||
case SEARCH -> postSearch(ex, path, user.get());
|
||||
default -> postTag(ex, module, path);
|
||||
};
|
||||
} catch (UmbrellaException e) {
|
||||
return send(ex, e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean postTag(HttpExchange ex, String module, Path path) throws IOException {
|
||||
try {
|
||||
var head = path.pop();
|
||||
long entityId = Long.parseLong(head);
|
||||
var json = json(ex);
|
||||
@@ -102,7 +114,7 @@ public class TagModule extends BaseHandler implements TagService {
|
||||
List<Long> userList = null;
|
||||
if (!json.has(USER_LIST)) throw missingField(USER_LIST);
|
||||
var ul = json.isNull(USER_LIST) ? null : json.get(USER_LIST);
|
||||
if (ul instanceof JSONArray arr){
|
||||
if (ul instanceof JSONArray arr) {
|
||||
userList = arr.toList().stream()
|
||||
.filter(elem -> elem instanceof Number)
|
||||
.map(Number.class::cast)
|
||||
@@ -115,8 +127,6 @@ public class TagModule extends BaseHandler implements TagService {
|
||||
return sendContent(ex, tag);
|
||||
} catch (NumberFormatException e) {
|
||||
return sendContent(ex, HTTP_UNPROCESSABLE, "Entity id missing in path.");
|
||||
} catch (UmbrellaException e) {
|
||||
return send(ex, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,6 +159,13 @@ public class TagModule extends BaseHandler implements TagService {
|
||||
return entry;
|
||||
}
|
||||
|
||||
private boolean postSearch(HttpExchange ex, Path path, UmbrellaUser user) throws IOException {
|
||||
var json = json(ex);
|
||||
if (!json.has(KEY)) throw missingField(KEY);
|
||||
var key = json.getString(KEY);
|
||||
return getTagUses(ex,key,user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(String module, long entityId, Collection<Long> userIds, Collection<String> tags) {
|
||||
tagDb.save(userIds,module,entityId,tags.stream().map(String::trim).toList());
|
||||
|
||||
Reference in New Issue
Block a user