implemented tag cloud
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
@@ -28,6 +28,7 @@
|
|||||||
import ResetPw from "./routes/user/ResetPw.svelte";
|
import ResetPw from "./routes/user/ResetPw.svelte";
|
||||||
import Search from "./routes/search/Search.svelte";
|
import Search from "./routes/search/Search.svelte";
|
||||||
import SendDoc from "./routes/document/Send.svelte";
|
import SendDoc from "./routes/document/Send.svelte";
|
||||||
|
import TagList from "./routes/tags/Index.svelte";
|
||||||
import TagUses from "./routes/tags/TagUses.svelte";
|
import TagUses from "./routes/tags/TagUses.svelte";
|
||||||
import TaskList from "./routes/task/Index.svelte";
|
import TaskList from "./routes/task/Index.svelte";
|
||||||
import Times from "./routes/time/Index.svelte";
|
import Times from "./routes/time/Index.svelte";
|
||||||
@@ -96,6 +97,7 @@
|
|||||||
<Route path="/project/:id/kanban" component={Kanban} />
|
<Route path="/project/:id/kanban" component={Kanban} />
|
||||||
<Route path="/project/:id/view" component={ViewPrj} />
|
<Route path="/project/:id/view" component={ViewPrj} />
|
||||||
<Route path="/search" component={Search} />
|
<Route path="/search" component={Search} />
|
||||||
|
<Route path="/tags" component={TagList} />
|
||||||
<Route path="/tags/use/:tag" component={TagUses} />
|
<Route path="/tags/use/:tag" component={TagUses} />
|
||||||
<Route path="/task" component={TaskList} />
|
<Route path="/task" component={TaskList} />
|
||||||
<Route path="/task/:parent_task_id/add_subtask" component={AddTask} />
|
<Route path="/task/:parent_task_id/add_subtask" component={AddTask} />
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ onMount(fetchModules);
|
|||||||
<a href="/company" {onclick}>{t('companies')}</a>
|
<a href="/company" {onclick}>{t('companies')}</a>
|
||||||
<a href="/project" {onclick}>{t('projects')}</a>
|
<a href="/project" {onclick}>{t('projects')}</a>
|
||||||
<a href="/task" {onclick}>{t('tasks')}</a>
|
<a href="/task" {onclick}>{t('tasks')}</a>
|
||||||
|
<a href="/tags" {onclick}>{t('tags')}</a>
|
||||||
<a href="/document" {onclick}>{t('documents')}</a>
|
<a href="/document" {onclick}>{t('documents')}</a>
|
||||||
<a href="/bookmark" {onclick}>{t('bookmarks')}</a>
|
<a href="/bookmark" {onclick}>{t('bookmarks')}</a>
|
||||||
<a href="/notes" {onclick}>{t('notes')}</a>
|
<a href="/notes" {onclick}>{t('notes')}</a>
|
||||||
|
|||||||
40
frontend/src/routes/tags/Index.svelte
Normal file
40
frontend/src/routes/tags/Index.svelte
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<script>
|
||||||
|
import { useTinyRouter } from 'svelte-tiny-router';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { api } from '../../urls.svelte';
|
||||||
|
import { error, yikes } from '../../warn.svelte';
|
||||||
|
import { t } from '../../translations.svelte';
|
||||||
|
|
||||||
|
let router = useTinyRouter();
|
||||||
|
let tags = $state(null);
|
||||||
|
|
||||||
|
function onclick(e){
|
||||||
|
e.preventDefault();
|
||||||
|
var target = e.target;
|
||||||
|
while (target && !target.href) target=target.parentNode;
|
||||||
|
let href = target.getAttribute('href');
|
||||||
|
if (href) router.navigate(href);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadTags(){
|
||||||
|
const url = api('tags');
|
||||||
|
const res = await fetch(url,{credentials:'include'});
|
||||||
|
if (res.ok){
|
||||||
|
yikes();
|
||||||
|
tags = await res.json();
|
||||||
|
} else error(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(loadTags)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h1>{t('tags')}</h1>
|
||||||
|
|
||||||
|
<div class="cloud">
|
||||||
|
{#if tags}
|
||||||
|
{#each tags as entry}
|
||||||
|
<a href="/tags/use/{encodeURIComponent(entry.tag)}" class="tag" style="font-size: {40 - 28/entry.count}px" title={t('count_of_occurrences',entry)} {onclick} >{entry.tag}</a>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
@@ -6,15 +6,12 @@ import static de.srsoftware.umbrella.core.ModuleRegistry.userService;
|
|||||||
|
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
import de.srsoftware.tools.Path;
|
import de.srsoftware.tools.Path;
|
||||||
import de.srsoftware.tools.SessionToken;
|
|
||||||
import de.srsoftware.umbrella.core.BaseHandler;
|
import de.srsoftware.umbrella.core.BaseHandler;
|
||||||
import de.srsoftware.umbrella.core.ModuleRegistry;
|
import de.srsoftware.umbrella.core.ModuleRegistry;
|
||||||
import de.srsoftware.umbrella.core.Util;
|
import de.srsoftware.umbrella.core.Util;
|
||||||
import de.srsoftware.umbrella.core.api.MarkdownService;
|
import de.srsoftware.umbrella.core.api.MarkdownService;
|
||||||
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
|
||||||
import de.srsoftware.umbrella.core.model.Token;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public class MarkdownApi extends BaseHandler implements MarkdownService {
|
public class MarkdownApi extends BaseHandler implements MarkdownService {
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ public class Constants {
|
|||||||
|
|
||||||
public static final String COMMENT_HASH = "comment_hash";
|
public static final String COMMENT_HASH = "comment_hash";
|
||||||
public static final String CONFIG_DATABASE = "umbrella.modules.tags.database";
|
public static final String CONFIG_DATABASE = "umbrella.modules.tags.database";
|
||||||
|
public static final String COUNT = "count";
|
||||||
public static final String TABLE_COMMENTS = "comments";
|
public static final String TABLE_COMMENTS = "comments";
|
||||||
public static final String TABLE_TAGS = "tags";
|
public static final String TABLE_TAGS = "tags";
|
||||||
public static final String TABLE_TAGS_NEW = "tags_new";
|
public static final String TABLE_TAGS_NEW = "tags_new";
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import static java.lang.System.Logger.Level.*;
|
|||||||
import static java.text.MessageFormat.format;
|
import static java.text.MessageFormat.format;
|
||||||
import static java.time.ZoneOffset.UTC;
|
import static java.time.ZoneOffset.UTC;
|
||||||
|
|
||||||
|
import de.srsoftware.tools.Tuple;
|
||||||
import de.srsoftware.tools.jdbc.Query;
|
import de.srsoftware.tools.jdbc.Query;
|
||||||
import de.srsoftware.umbrella.bookmarks.BookmarkDb;
|
import de.srsoftware.umbrella.bookmarks.BookmarkDb;
|
||||||
import de.srsoftware.umbrella.core.BaseDb;
|
import de.srsoftware.umbrella.core.BaseDb;
|
||||||
@@ -269,6 +270,24 @@ CREATE TABLE IF NOT EXISTS {0} (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<Tuple<String,Long>> list(long userId) {
|
||||||
|
try {
|
||||||
|
var rs = select("tag, COUNT(tag) as count").from(TABLE_TAGS).where(USER_ID,equal(userId)).groupBy(TAG).sort(TAG).exec(db);
|
||||||
|
var list = new ArrayList<Tuple<String,Long>>();
|
||||||
|
while (rs.next()) {
|
||||||
|
var tag = rs.getString(TAG);
|
||||||
|
if (tag.isBlank()) continue;;
|
||||||
|
var count = rs.getLong("count");
|
||||||
|
list.add(Tuple.of(tag,count));
|
||||||
|
}
|
||||||
|
rs.close();
|
||||||
|
return list;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw databaseException("Failed to load tags for user {0}",userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<Long, ? extends Collection<String>> list(long userId, String module, Collection<Long> entityIds) {
|
public Map<Long, ? extends Collection<String>> list(long userId, String module, Collection<Long> entityIds) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
package de.srsoftware.umbrella.tags;
|
package de.srsoftware.umbrella.tags;
|
||||||
|
|
||||||
|
|
||||||
|
import de.srsoftware.tools.Tuple;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -14,6 +15,8 @@ public interface TagDB {
|
|||||||
|
|
||||||
Map<String, List<Long>> getUses(String tag, long id);
|
Map<String, List<Long>> getUses(String tag, long id);
|
||||||
|
|
||||||
|
Collection<Tuple<String, Long>> list(long userId);
|
||||||
|
|
||||||
Set<String> list(long userId, String module, long entityId);
|
Set<String> list(long userId, String module, long entityId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ import static de.srsoftware.umbrella.core.ModuleRegistry.userService;
|
|||||||
import static de.srsoftware.umbrella.core.ResponseCode.HTTP_UNPROCESSABLE;
|
import static de.srsoftware.umbrella.core.ResponseCode.HTTP_UNPROCESSABLE;
|
||||||
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingFieldException;
|
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingFieldException;
|
||||||
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.unprocessable;
|
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.unprocessable;
|
||||||
import static de.srsoftware.umbrella.tags.Constants.CONFIG_DATABASE;
|
import static de.srsoftware.umbrella.tags.Constants.*;
|
||||||
import static de.srsoftware.umbrella.tags.Constants.TAG;
|
|
||||||
|
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
import de.srsoftware.configuration.Configuration;
|
import de.srsoftware.configuration.Configuration;
|
||||||
@@ -67,7 +66,7 @@ public class TagModule extends BaseHandler implements TagService {
|
|||||||
var user = userService().refreshSession(ex);
|
var user = userService().refreshSession(ex);
|
||||||
if (user.isEmpty()) return unauthorized(ex);
|
if (user.isEmpty()) return unauthorized(ex);
|
||||||
var module = path.pop();
|
var module = path.pop();
|
||||||
if (module == null) throw unprocessable("Module missing in path.");
|
if (module == null) return getUserTags(ex, user.get());
|
||||||
var head = path.pop();
|
var head = path.pop();
|
||||||
if (USES.equals(module)) return getTagUses(ex,head,user.get());
|
if (USES.equals(module)) return getTagUses(ex,head,user.get());
|
||||||
long entityId = Long.parseLong(head);
|
long entityId = Long.parseLong(head);
|
||||||
@@ -79,10 +78,6 @@ public class TagModule extends BaseHandler implements TagService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean getTagUses(HttpExchange ex, String tag, UmbrellaUser user) throws IOException {
|
|
||||||
return sendContent(ex,tagDb.getUses(tag,user.id()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean doPost(Path path, HttpExchange ex) throws IOException {
|
public boolean doPost(Path path, HttpExchange ex) throws IOException {
|
||||||
addCors(ex);
|
addCors(ex);
|
||||||
@@ -117,6 +112,10 @@ public class TagModule extends BaseHandler implements TagService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean getTagUses(HttpExchange ex, String tag, UmbrellaUser user) throws IOException {
|
||||||
|
return sendContent(ex,tagDb.getUses(tag,user.id()));
|
||||||
|
}
|
||||||
|
|
||||||
public Collection<String> getTags(String module, long entityId, UmbrellaUser user) throws UmbrellaException{
|
public Collection<String> getTags(String module, long entityId, UmbrellaUser user) throws UmbrellaException{
|
||||||
return tagDb.list(user.id(),module,entityId);
|
return tagDb.list(user.id(),module,entityId);
|
||||||
}
|
}
|
||||||
@@ -126,6 +125,11 @@ public class TagModule extends BaseHandler implements TagService {
|
|||||||
return tagDb.list(user.id(),module,entityIds);
|
return tagDb.list(user.id(),module,entityIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean getUserTags(HttpExchange ex, UmbrellaUser user) throws IOException {
|
||||||
|
var tuples = tagDb.list(user.id()).stream().filter(t -> t.a != null && t.b != null);
|
||||||
|
return sendContent(ex,tuples.map(t -> Map.of(TAG,t.a,COUNT,t.b)));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void save(String module, long entityId, Collection<Long> userIds, Collection<String> tags) {
|
public void save(String module, long entityId, Collection<Long> userIds, Collection<String> tags) {
|
||||||
tagDb.save(userIds,module,entityId,tags);
|
tagDb.save(userIds,module,entityId,tags);
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
"contained_tax": "enthaltene Steuer",
|
"contained_tax": "enthaltene Steuer",
|
||||||
"content": "Inhalt",
|
"content": "Inhalt",
|
||||||
"context": "Kontext",
|
"context": "Kontext",
|
||||||
|
"count_of_occurrences": "{count} Verwendungen",
|
||||||
"country": "Land",
|
"country": "Land",
|
||||||
"COURT": "Amtsgericht",
|
"COURT": "Amtsgericht",
|
||||||
"CUSTOMER-NUMBER": "Kundennummer",
|
"CUSTOMER-NUMBER": "Kundennummer",
|
||||||
|
|||||||
@@ -423,4 +423,13 @@ fieldset.vcard{
|
|||||||
|
|
||||||
.vcard td button.symbol{
|
.vcard td button.symbol{
|
||||||
float: right;
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cloud .tag{
|
||||||
|
border: 1px solid;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 2px;
|
||||||
|
margin: 0 6px;
|
||||||
|
white-space: nowrap;
|
||||||
|
display: inline flow-root;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user